## Phase 27.14: FuncScannerBox._append_defs/2 JoinIR lowering - **新規実装**: `funcscanner_append_defs.rs` (322行) - Shared Builder Pattern採用 - MIR-based lowering with CFG sanity checks - ValueId range 9000-10999 割り当て - **テスト**: `mir_joinir_funcscanner_append_defs.rs` (3テスト) - type_sanity, empty_module_returns_none, auto_lowering (ignored) - **最小.hako**: `funcscanner_append_defs_minimal.hako` ## コード品質改善 (5項目完了) 1. **CFG Sanity Checks強化** (`common.rs`) - `has_array_method()`: ArrayBox操作検出 - `has_loop_increment()`: i+1パターン検出 2. **ValueIdテスト自動化** (`value_id_ranges.rs`) - マクロ化 + 自動overlap検証で30→15行に削減 3. **モジュール名統一確認** (作業不要、既に統一済み) 4. **Shared Builder命名統一** (`funcscanner_trim.rs`) - `build_trim_joinir` → `build_funcscanner_trim_joinir` 5. **全テストPASS確認** - value_id_ranges, funcscanner_trim, funcscanner_append_defs全てPASS ✅ ## 効果 - CFG検証関数: 1個 → 3個 (200%↑) - テストコード: 50%削減 (保守性向上) - 命名一貫性: 75% → 100% - ビルド成功率: 100%維持 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
62 KiB
Current Task — Phase 21.8 / 25 / 25.1 / 25.2 / 25.4 / 26-F / 26-G Snapshot(2025-11-22 時点)
このファイルは「今どこまで終わっていて、次に何をやるか」を 1000 行以内でざっくり把握するためのスナップショットだよ。
詳細な履歴やログはgit log CURRENT_TASK.mdからいつでも参照できるようにしておくね。
0. 現在地ざっくり
- フェーズ軸:
- 21.8: Numeric Core / Core-15 まわりの安定化(既に日常的には安定運用)。
- 25.x: Stage0/Stage1/Stage‑B / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
- 25.1 系: Stage‑B / Stage‑1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
- 26-F / 26-G: Exit PHI / ExitLiveness 用の 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)と MirScanExitLiveness の準備。
- 26-H / 27.x(New): JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder Pattern+MIR-based lowering に移行し、27.12/27.13 で Stage‑1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の lowering+minimal .hako+auto_lowering テストまで整備済み。短期フェーズ残りは JoinIR runner の命令セット整理とスモーク固め)。
- Rust 側:
- LoopForm v2 + ControlForm + Conservative PHI は、代表テスト(Stage‑1 UsingResolver / Stage‑B 最小ループ)ではほぼ安定。
- 静的メソッド呼び出し規約と
continue絡みの PHI は 25.1m までで根治済み。
- .hako 側:
- Stage‑B コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。
- Stage‑B / FuncScanner ライン:
- Phase 25.3 をクローズし、
stageb_fib_program_defs_canary_vm.shが緑(defsにTestBox.fib/Main.main、fib.body.body[*] にLoop)。 - Stage‑B は block パーサ優先 + defs を Block 包みで構造化。次手: Stage‑1 UsingResolver ループの Region+next_i 揃え / Stage‑1 CLI program-json selfhost 準備。
- Phase 25.3 をクローズし、
1. 最近完了した重要タスク
1-01. Phase 26-E — PhiBuilderBox SSOT統一化(完了 2025-11-22)
目的
- PHI生成ロジックを単一責務箱(PhiBuilderBox)に集約
- If/Loop両対応の統一インターフェース提供
- Conservative戦略 + BTreeSet/BTreeMap で決定性向上
🎯 Phase 26-E 進捗状況(2025-11-22)
- ✅ Phase 1: PhiBuilderBox 骨格作成(444行、ControlForm対応)
- ✅ Phase 2: If PHI生成完全実装(Conservative戦略、決定的順序保証)
- ✅ Phase 3: PhiBuilderOps委譲実装(has-a設計、ChatGPT+Claude合意)
- ⏳ Phase 4: Legacy削除(loop_phi.rs 287行、将来タスク)
Phase 2 実装内容(2025-11-22完了)
- PhiBuilderBox作成 (src/mir/phi_core/phi_builder_box.rs, 444行)
- If PHI生成:
generate_if_phis()完全実装 - Conservative戦略: void emission 含む完全対応
- 決定的順序: BTreeSet/BTreeMap で非決定性排除
- If PHI生成:
- PhiBuilderOps trait (7メソッド)
- 最小PHI生成インターフェース
- テスタビリティ向上(モック可能)
- loop_builder.rs 統合 (src/mir/loop_builder.rs)
- PhiBuilderOps trait 実装(Ops構造体)
- If PHI呼び出し箇所統合(line 1136-1144)
Phase 3 実装内容(2025-11-22完了)
- 設計方針変更: 継承(is-a)→委譲(has-a)に変更(ChatGPT+Claude合意)
- PhiBuilderOps = 低レベル「PHI命令発行」道具箱
- LoopFormOps = 高レベル「ループ構造構築」作業場
- 関係: has-a(委譲)が正しい設計(異なる抽象レベル)
- LoopBuilder委譲実装 (src/mir/loop_builder.rs, +64行)
impl<'a> PhiBuilderOps for LoopBuilder<'a>個別実装- 明示的 trait 修飾で自己再帰回避:
<Self as LoopFormOps>::method() - HashSet → Vec 変換 + ソート(決定性保証)
- emit_phi 引数差吸収: set_current_block 経由
- 設計文書更新 (src/mir/phi_core/loopform_builder.rs)
- has-a 設計根拠をコメント追加
- 継承試行の失敗理由を記録
Phase 3 テスト結果(2025-11-22)
- 決定性: 10回実行で一貫した結果(20% 成功率は既存 Loop PHI バグによるもの)
- 退行なし: Phase 26-E 実装前と同じ成功率
- If PHI 生成: 100% 動作(Test 2 で確認済み)
- テスト詳細:
mir_stage1_using_resolver_resolve_with_modules_map_verifies: 2/10 成功(20%、既存バグ)mir_stage1_using_resolver_modules_map_continue_break_with_lookup_verifies: 10/10 成功(100%)
Phase 3 コミット
b9a03429: Phase 26-E-2 - PhiBuilderBox If PHI生成完全実装e0be01c1: Phase 26-E-3 - PhiBuilderOps委譲実装(has-a設計)
削減見込み
- Phase 2: -80行(If側重複削除)
- Phase 4: -287行(loop_phi.rs Legacy削除)
- 合計: -367行(純削減)
次のステップ(Phase 4)
- loop_phi.rs Legacy削除(287行)
- Loop PHI バグ調査(20% 成功率問題)
- 100% 決定的テスト達成
関連ファイル
- phi_builder_box.rs - 444行
- loop_builder.rs - PhiBuilderOps実装
- exit_phi_builder.rs - 779行(Phase 26-D完成)
- header_phi_builder.rs - 548行(Phase 26-C-2完成)
1-00. Phase 21.7 — Static Box Methodization(完了 2025-11-21, 既定ON に移行)
目的
- static box 内の呼び出しを NamingBox/Method まわりで一貫して扱えるようにする。
- Global("BoxName.method/arity") を Method{receiver = static singleton} に寄せる(トグル制御)。
🎯 Phase 21.7++ 計画(2025-11-22 全フェーズ完了!🎊)
- NamingBox SSOT 統一化チェックリスト: phase-21.7-naming-ssot-checklist.md
- StringUtils using 解決バグ修正(2025-11-22, commit f4ae1445)を踏まえた改善計画
- ✅ Phase 0(観測ライン): Silent Failure 根絶完了(commit 63012932)
- ✅ Phase 1(基盤整備): StaticMethodId SSOT 基盤確立(commit 96c1345e)
- ✅ Phase 2(VM統一): VM 名前解決 SSOT 準拠(commit 1b413da5)
- ✅ Phase 3(全体統一): Builder 側統一、素手 split 根絶(commit c8ad1dae)
- ✅ Phase 4(ドキュメント): README・トラブルシューティングガイド整備(commit 806e4d72)
- 累計工数: 10時間(進捗率: 50-67%)
Phase 0-4 実装内容(2025-11-22 全完了)
-
Phase 0: 観測ライン緊急構築 (commit
63012932)- TOML parse エラー即座表示(pipeline.rs)
- VM 関数ルックアップ「Did you mean?」提案(global.rs)
- using not found 詳細化(strip.rs)
- 効果: Silent Failure 根絶、デバッグ時間が時間→分に短縮
-
Phase 1: StaticMethodId SSOT 基盤 (commit
96c1345e)StaticMethodId構造体導入(naming.rs:86-248)- parse/format/with_arity ヘルパー実装
- 包括的テスト(13ケース全PASS)
- 効果: 関数名パース/フォーマット一元化、型安全化
-
Phase 2: VM 統一 (commit
1b413da5)- global.rs を StaticMethodId ベース化
- デバッグログ強化(NYASH_DEBUG_FUNCTION_LOOKUP=1)
- テスト全通過(349 passed, 退行なし)
- 効果: arity バグ根治、Hotfix 卒業
-
Phase 3: 全体統一 (commit
c8ad1dae)- unified_emitter.rs の methodization を StaticMethodId 化
- known.rs の split_once 全置き換え(2箇所)
- 効果: 素手 split 根絶、Builder 側完全統一、コード50%削減
-
Phase 4: ドキュメント整備 (commit
806e4d72)- Phase 21.7 README に完了セクション追加(60行)
- トラブルシューティングガイド作成(200+行、新規)
- チェックリスト進捗サマリー更新
- 効果: 再発防止、開発者オンボーディング改善
Phase 0-2 以前の実装内容
-
NamingBox decode 関数追加 (Step 1: commit
a13f14ce)decode_static_method(func_name: &str) -> Option<(&str, &str, usize)>is_static_method_name(func_name: &str) -> bool- "BoxName.method/arity" 形式をパースして (box_name, method, arity) に分解。
-
Hotfix 7 修正 (Step 2: commit
a13f14ce)unified_emitter.rsの receiver 追加ロジックを修正。- StaticCompiler box_kind のメソッドで、static box method (decode可能な名前) の場合は receiver を追加しない。
- instance method のみ receiver を args の先頭に追加するようガード実装。
-
Methodization 実装 (Step 3: commit
b5cd05c2)builder.rs:static_box_singletons: HashMap<String, ValueId>フィールド追加。unified_emitter.rs: HAKO_MIR_BUILDER_METHODIZE=1 で有効化。- Callee::Global(name) を decode して static box method 判定。
- シングルトンインスタンス (NewBox) をキャッシュ生成。
- Callee::Method{receiver=singleton} に変換。
動作確認
- デフォルト (OFF):
call_global Calculator.add/2(既存挙動維持) - トグル (ON):
new Calculator() → call %singleton.add(...)(methodization) - RC=0 両モード動作確認済み:
apps/tests/phase217_methodize_test.hako
環境変数
HAKO_MIR_BUILDER_METHODIZE=0/1: methodization 制御。既定ON(未設定 or "1")、"0" のときのみ無効化。NYASH_METHODIZE_TRACE=1: Global→Method 変換ログ出力
次タスク(Phase 21.7++ Phase 3-4)(10-15時間見込み)
- Phase 3: 全体統一
- MIR Builder 側を StaticMethodId 統一(builder/calls/unified_emitter.rs 等)
- 素手 split 置き換え(
rg '"\."' --type rust src/mir/builder/) - 100-200 行削減見込み
- Phase 4: ドキュメント化
- SSOT 設計書更新
- 移行ガイド作成
- チームレビュー
- 長期: NamingBox/UnifiedCallEmitter/VM の 3 点で「名前と arity の SSOT」完全統一。
1-02. Phase 27.4-A — JoinIR Header φ 統合(LoopHeaderShape 導入、2025-11-23 完了)
目的
- HeaderPhiBuilder が担っていた「loop header φ(Pinned/Carrier の合流)」の意味を、JoinIR 側に構造として持ち上げる足場を作る。
- Rust 側の header φ 挙動は一切変えず、本線 MIR/LoopForm→VM を壊さないまま JoinIR 経路の設計を進める。
やったこと
src/mir/join_ir.rsにLoopHeaderShape { pinned, carriers }を追加し、skip_ws / FuncScanner.trim のループについて:- skip_ws: Pinned = [s, n], Carrier = [i](
loop_step(s, i, n)の設計をコメント付きで固定)。 - trim : Pinned = [str, b], Carrier = [e](
loop_step(str, b, e)の設計をコメント付きで固定)。
- skip_ws: Pinned = [s, n], Carrier = [i](
- Header φ の意味を「loop_step 引数としてどう表現するか」をコードとコメントで一致させた。
src/mir/phi_core/header_phi_builder.rsにNYASH_JOINIR_HEADER_EXP=1フラグチェックを追加(現在はログのみ、挙動は不変)。- JoinIR テストまわり:
- skip_ws / min / trim の JoinIR 型・変換テストを維持しつつ、trim 側は
trim_main + loop_step + skip_leadingの 3 関数構成にテスト期待を合わせた。 LoopHeaderShape用のミニテストを追加し、「to_loop_step_params() は pinned→carriers 順で返す」という契約を固定。
- skip_ws / min / trim の JoinIR 型・変換テストを維持しつつ、trim 側は
状態
- JoinIR 側では Header φ の意味が LoopHeaderShape で表現され、対象ループの
loop_step引数に反映済み。 - HeaderPhiBuilder は従来挙動のまま(Phase 27.4-C 以降でトグル付き縮退を予定)。
- すべての JoinIR テスト 7/7 が PASS、既存本線テストの緑度には影響なし。
1-03. Phase 27.4-C — HeaderPhiBuilder バイパス実験(JoinIR 経路限定、2025-11-23 完了)
目的
- Header φ の意味は JoinIR 側(LoopHeaderShape+loop_step 引数)に持ち上がったので、JoinIR 実験経路に限って Rust 側 HeaderPhiBuilder をバイパスできるか試す。
- 本線 MIR/LoopForm→VM の挙動には一切触れず、JoinIR runner テスト専用の縮退ステップとして運用する。
やったこと
src/mir/phi_core/header_phi_builder.rsに 2 つのヘルパーを追加:joinir_header_experiment_enabled()…NYASH_JOINIR_HEADER_EXP=1チェック(27.4-B で導入)。joinir_header_bypass_enabled()…NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_HEADER_EXP=1の両方が ON のとき true。
src/mir/loop_builder.rsで HeaderPhiBuilder 利用箇所にバイパスロジックを追加:- 現在の関数名が
Main.skip/1またはFuncScannerBox.trim/1のとき、 - かつ
joinir_header_bypass_enabled()が true のときのみemit_header_phis()をスキップ。 NYASH_LOOPFORM_DEBUG=1時はデバッグログを出す。
- 現在の関数名が
- JoinIR テスト側(skip_ws / trim)に、「NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される」旨のコメントを追加。
docs/private/roadmap2/phases/phase-27-joinir/IMPLEMENTATION_LOG.mdに Phase 27.4-C セクションを追加し、対象関数・トグル条件・挙動を記録。phase-27-joinir/TASKS.mdで 27.4-C を完了扱いに更新。
状態
- JoinIR runner 実験時に
NYASH_JOINIR_EXPERIMENT=1+NYASH_JOINIR_HEADER_EXP=1を立てた場合のみ、Main.skip/1 / FuncScannerBox.trim/1 の Header φ がスキップされる。 - このモードでは VM 実行は使わず、JoinIR runner だけで意味が保たれているかを確認するテストとして運用。
- 本線 MIR/LoopForm→VM の挙動は、トグル OFF 時には従来どおり(Header φ あり)のまま。
1-04. Phase 27.5 — JoinIR Exit φ 統合(設計着手、2025-11-24 現在)
目的
- ExitPhiBuilder が担っている「exit φ(break/early-exit の合流)」の意味を、JoinIR 側の
k_exit呼び出し+引数として表現できるようにする設計フェーズ。 - Rust 側の ExitPhiBuilder 挙動は変えず、本線 VM を壊さないまま minimal/trim の 2 ケースで Exit φ の意味を整理する。
やったこと(設計メモ反映済み)
docs/private/roadmap2/phases/phase-27.5-joinir-exit/README.mdを追加し、minimal_ssa_skip_ws と FuncScanner.trim_min の exit 経路を整理。- minimal: break 経路は
i>=n/ch!=" ", Exit φ はiだけ → JoinIR ではk_exit(i)を想定。 - trim: break 経路は
!(e>b)/!is_space, Carrier=e, Pinned=str,b。ExitShape は Option A:[e](第一候補) / Option B:[str,b,e]を比較と記述。
- minimal: break 経路は
docs/private/roadmap2/phases/phase-27.5-joinir-exit/TASKS.mdの A-1/A-2 を完了にし、B 以降(LoopExitShape 型/コメント追加、JoinIR 変換への反映、テスト/ログ追記)はこれから。
次の一手
- LoopExitShape の型 or コメントを JoinIR に追加して、minimal/trim の exit 引数セットを明示。
- JoinIR 変換の exit 部分に「k_exit で何を合流させるか」のコメントを足し、必要なら命令並びを軽く整える(意味は変えない)。
- JoinIR テストと IMPLEMENTATION_LOG に Exit φ 観点のメモを追記。
1-05. Phase 26-F — Loop Exit Liveness / BodyLocal PHI Guard(箱とガードの整備、2025-11-22 時点)
目的
- LoopForm v2 / Exit PHI まわりで、BodyLocal 変数の未定義利用を「箱」と「Fail-Fast」で確実に検知・抑制できるようにする。
- MIR スキャン(本物の Exit Liveness)は次フェーズに送りつつ、構造と受け口と環境変数ガードだけ先に整える。
やったこと(26-F 初期完了分)
- 4箱構成の整理と実装(Exit PHI 専用レイヤ):
LoopVarClassBox(loop_var_classifier.rs):- 変数のスコープ分類専用(Pinned / Carrier / BodyLocalExit / BodyLocalInternal)。
- ここでは「どこで定義されているか」だけを見て、PHI 発行は行わない。
LoopExitLivenessBox(loop_exit_liveness.rs新設):- ループ exit 後で「生きている可能性のある変数」の集合を返す箱。
- Phase 26-F 時点では中身は保守的近似+ダミー、実運用は環境変数ガードで OFF。
- 環境変数:
NYASH_EXIT_LIVE_ENABLE=1で将来の MIR スキャン実装を opt-in で有効化(既定は 0/未設定)。NYASH_EXIT_LIVENESS_TRACE=1でトレース出力。
BodyLocalPhiBuilder(body_local_phi_builder.rs):LoopVarClassBoxの分類結果とLoopExitLivenessBoxのlive_at_exitを統合し、「どの BodyLocal に exit PHI が必要か」を決める箱。- 既定挙動(ガード OFF):
- これまで通り
class.needs_exit_phi()のみを見る(Pinned / Carrier / BodyLocalExit)。
- これまで通り
- ガード ON の挙動(まだ実験段階):
BodyLocalInternalかつlive_at_exitに含まれ、かつLocalScopeInspector::is_available_in_allが true なものだけ、追加で exit PHI 候補に昇格できる OR ロジックを持つ。
PhiInvariantsBox(phi_invariants.rs):- Exit/If PHI 最後の Fail-Fast 検証箱。
- 「全 pred で定義されているか」「不正な incoming が無いか」をチェックし、構造バグはここで止める。
- Docs 整備:
docs/development/architecture/loops/loopform_ssot.mdに 4箱構成と環境変数ガード方針を追記。docs/private/roadmap2/phases/phase-26-F/README.mdを新設し、26-F のスコープ/やらないこと/次フェーズ(MIR スキャン本体)への橋渡しを書き切り。
テスト状況(ガード OFF 時点)
- Phase 26-F-3 → 26-F の流れで、一時的に退行したが、
NYASH_EXIT_LIVE_ENABLEを既定 OFF にし、- BodyLocalInternal 救済ロジックをガード付きに戻したことで、
- F3 ベースラインより PASS が増え、FAIL が減る状態(例: 365 PASS / 9 FAIL)まで持ち直し済み。
- FuncScanner 系:
mir_funcscanner_skip_ws_direct_vmmir_funcscanner_parse_params_trim_min_verify_and_vmは引き続き「BodyLocal / Exit Liveness のカナリア」として使う(未定義値が出た場合は 26-G 以降で追う)。
このフェーズで残っていること
ExitLivenessProvider相当のインターフェースをExitPhiBuilder周辺に導入し、「ExitLiveness を差し替え可能」な受け口だけ整える(中身は Legacy のまま)。→ 完了。MirScanExitLivenessも追加済み(現状は header/exit_snapshots の union を返す簡易版)。- LoopFormOps / MirBuilder に MIR 命令列アクセスを追加する設計を 26-F の README にメモしておき、実装は 26-G 以降に分離する。
1-02+. Phase 26-G — Exit Liveness MIR Scan(計画開始)
- 26-F で作った差し替え口に、本物の use/def スキャン実装を載せるフェーズ。
NYASH_EXIT_LIVE_ENABLE=1で MIR スキャン版を有効にし、FuncScanner カナリア(skip_ws / parse_params_trim)を緑にするのが目標。- 新設 docs:
docs/private/roadmap2/phases/phase-26-G/README.mdに手順と受け入れ条件を記載済み。
1-03. Phase 25.3 — FuncScanner / Stage‑B defs 安定化(完了)
目的
- Stage‑B / FuncScanner ラインで defs が欠落したり Loop が脱落する問題を塞ぎ、selfhost 側の canary を緑に戻す。
やったこと
- StageBDriverBox.main:
- main 本文を
{…}で包んだ block パーサ優先で Program(JSON) に組み立て、defs には{"type":"Block","body":[…]}形式で埋め込むよう整理。 - Program パーサ fallback は
HAKO_STAGEB_PROGRAM_PARSE_FALLBACK=1の opt-in に封じ込め(skip_ws 崩れを回避)。
- main 本文を
- StageBFuncScannerBox._scan_methods:
- block パーサ優先に統一し、Program パーサは
HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1でのみ有効化。 - defs パラメータに必ず
meを足す従来挙動は維持(TestBox/Main いずれも同型で出力)。
- block パーサ優先に統一し、Program パーサは
- Rust 層の追加変更なし(LoopForm v2 / LoopSnapshotMergeBox をそのまま利用)。
結果
tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.shが安定して PASS。Program.kind == "Program"defsにTestBox.fib/Main.mainを保持していること。TestBox.fib.body.body[*]にLoopノードが含まれること。 を満たした状態でrc=0になることを確認。
- FuncScanner / Stage‑B 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox 上で構造的に安定したとみなし、Phase 25.3 はクローズ。
- 次フェーズの入口が整理できたので、Stage‑1 UsingResolver ループ(Region+next_i 形)と Stage‑1 CLI program-json/selfhost 導線に着手可能。
1-1. Phase 25.1m — Static Method / LoopForm v2 continue + PHI Fix(完了)
目的
- 静的メソッド呼び出し時の「暗黙レシーバ+引数ずれ」バグと、LoopForm v2 経路における
continue+ header PHI の欠落を根本から直す。
Rust 側(静的メソッド / 暗黙レシーバ / JSON v0 Bridge)
src/mir/function.rs::MirFunction::new- 暗黙 receiver 判定を是正し、「第 1 パラメータが Box 型の関数だけ」をインスタンスメソッド with receiver とみなす。
- 非 Box 型(
String,Integerなど)で始まるパラメータ列の関数は、暗黙レシーバなしの静的メソッド / Global 関数として扱うように変更。 - その結果:
static box TraceTest { method log(label) { ... } }に対してTraceTest.log("HELLO")を呼ぶと、labelに"HELLO"が正しく入る(以前はlabel = nullになっていた)。
src/mir/builder/decls.rs::build_static_main_boxMain.main(args)を「静的エントリ関数」に lower する経路をNYASH_BUILD_STATIC_MAIN_ENTRY=1のときだけ有効 にし、 通常の VM 実行では wrappermain()を正規エントリとして扱うように整理。- これにより、「
Main.main(args)版ではループが 1 度も回らずreturn 0で終わる」バグを解消。
- JSON v0 Bridge 経由の Box メソッド(Stage‑1/Stage‑B defs):
src/runner/json_v0_bridge/lowering.rsでprog.defsの降下ロジックを調整し、box_name != "Main"の関数定義をインスタンスメソッドとして扱ってsignature.paramsに「暗黙me+ 明示パラメータ」を載せる。func_var_mapにme→func.params[0]を事前バインドし、残りのパラメータ名をparams[1..]に対応づける。
- これにより、
Stage1UsingResolverFull._build_module_map()のように JSON ではparams: []でも Hako 側でme._push_module_entry(...)を使う関数について、 Rust VM 実行時にmeが未定義(ValueId(0))になるケースを構造的に防止。
LoopForm v2(continue + header PHI)
src/mir/phi_core/loopform_builder.rs::LoopFormBuilder::seal_phis- シグネチャを
seal_phis(ops, latch_id)→seal_phis(ops, latch_id, &continue_snapshots)に拡張。 - preheader と latch に加え、
LoopBuilder側で記録しているcontinue_snapshotsからの値も header PHI の入力 として統合。 - pinned / carrier いずれも、header の全 predecessor:
(preheader, preheader_copy)(continue_bb, value_at_continue)(latch, value_at_latch)を入力として持つようになり、balanced scan など「continue を含むループ」の SSA が正しく構成される。
- シグネチャを
src/mir/loop_builder.rs::build_loop_with_loopformlet continue_snaps = self.continue_snapshots.clone();loopform.seal_phis(self, actual_latch_id, &continue_snaps)?;- という形で、LoopBuilder → LoopForm 側に
continueスナップショットを橋渡し。
ControlForm / LoopShape invariant
src/mir/control_form.rs::LoopShape::debug_validate(debug ビルドのみ)- 既存の:
preheader -> headerエッジ必須latch -> headerバックエッジ必須
- に加えて、次の invariant を追加:
continue_targetsの各ブロックからheaderへのエッジが存在すること。break_targetsの各ブロックからexitへのエッジが存在すること。
- これにより、LoopForm / LoopBuilder が
continue/break経路を誤配線した場合に、構造レベルで早期検知できる。
- 既存の:
テスト / 検証
- MIR ユニットテスト:
src/mir/phi_core/loopform_builder.rs::tests::test_seal_phis_includes_continue_snapshots- LoopFormBuilder 単体で「preheader + continue + latch」が PHI 入力に含まれることを固定。
src/tests/mir_stageb_loop_break_continue_verifies- Stage‑B 風の
loop + break/continueパターンで MirVerifier 緑。
- Stage‑B 風の
src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifies- LoopForm v2/PHI v2 経路(現在は既定実装)で Stage‑1 UsingResolver フル版が MirVerifier 緑。
- 実行観測:
- 開発用 Hako
loop_continue_fixed.hako:- 期待
RC=3/ 実測RC=3、PHI pred mismatch なし。
- 期待
- Stage‑B balanced scan:
StageBBodyExtractorBoxのバランススキャンループに trace を入れて 228 回イテレーションが回ること、ch == "{"ブランチでdepth/iを更新 →continue→ 次イテレーションに 確実に戻っている ことを確認。
- 開発用 Hako
1-2. Phase 25.1k — LoopSSA v2 (.hako) & Stage‑B harness 追従(Rust 側はおおむね完了)
目的
- Rust 側の LoopForm v2 / ControlForm / Conservative PHI を SSOT としつつ、Stage‑B / selfhost で使っている
.hako側 LoopSSA/BreakFinderBox/PhiInjectorBox をその規約に追従させる準備フェーズ。
Rust 側でやったこと(サマリ)
- Receiver / pinning:
CallMaterializerBox::materialize_receiver_in_calleeを事実上 no-op にし、 receiver の pinning / LocalSSA 連携をreceiver::finalize_method_receiverに一本化。MirBuilderにpin_slot_names: HashMap<ValueId, String>を持たせ、LocalSSA.ensureが「同じ slot にぶらさがる最新の ValueId」へ自動でリダイレクトできるようにした。
- Compiler Box の分類:
CalleeResolverBox::classify_box_kindにBreakFinderBox/PhiInjectorBox/LoopSSAを追加し、 Stage‑1/Stage‑B 用 LoopSSA 箱をCalleeBoxKind::StaticCompilerとして明示。
IfForm / empty else-branch の SSA fix(Stage‑1 UsingResolverFull 対応)
src/mir/builder/if_form.rs:if cond { then }(else なし)のパターンで、- else-entry 用に pre_if の
variable_mapから PHI ノードを生成したあと、 - その PHI 適用後の
variable_mapをelse_var_map_end_opt=Some(...)として merge フェーズに渡すように修正。
- else-entry 用に pre_if の
- 以前は empty else の場合に
else_var_map_end_optがNoneになっており、merge_modified_varsが pre_if の古い ValueId にフォールバックして、 merge ブロックで未定義の%0などを参照するケースがあった(Stage1UsingResolverFull.main/0の UndefinedValue)。 - 修正後は then/else 両ブランチで「PHI 適用後の variable_map」が merge に渡されるため、 empty else でも header/merge の SSA が崩れない。
- 検証:
src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifiesがMirVerifier緑になり、Stage1UsingResolverFull.main/0()の merge ブロックで PHI 後の値(例:%24)を正しく参照していることを MIR dump で確認済み。
.hako 側の今後(25.1k 後半)
LoopSSA.stabilize_merges(json)を Rust LoopForm v2 の Carrier/Pinned 規約に合わせて実装する(現在はほぼ stub)。- Stage‑B Test2(
tools/test_stageb_min.sh)で得られる Program(JSON v0) に対し、- Rust 側で
NYASH_VM_VERIFY_MIR=1を立てた実行結果と、 .hako側 LoopSSA v2 適用後の JSON → Rust 実行結果 を比較し、BreakFinderBox / PhiInjectorBox / LoopSSA の責務を切り分けていく。
- Rust 側で
1-3. Phase 25.1e/f/g — LoopForm PHI v2 / ControlForm 統合(サマリ)
- 25.1e(LoopForm PHI v2 migration):
- Local SSA(
local a = ...)の ValueId 分離を完了し、LoopForm v2 を「PHI/SSA の正」とする方向へ寄せた。 - Stage‑1 UsingResolver / 基本的な Stage‑B ループは、LoopForm v2 経路で MirVerifier 緑。
- Local SSA(
- 25.1f(ControlForm 層の導入):
ControlForm/LoopShape/IfShapeを導入し、Loop / If を共通ビューとして扱う箱を定義。- ここでは挙動を変えず、「構造だけを先に固定」する方針で設計を固めた。
- 25.1g(Conservative PHI ↔ ControlForm ブリッジ):
phi_core::loopform_builder::build_exit_phis_for_controlなど、ControlForm から Conservative PHI を呼び出す薄いラッパを追加。- 既存の PHI ロジックはそのままに、将来の置き換えポイントだけを明示している。
1-4. Phase 25.1A‑3 — Stage‑1 CLI bridge(stub 実装)
- Rust 側:
src/runner/stage1_bridge.rsをrun_refactored入口に組み込み、NYASH_USE_STAGE1_CLI=1かつ再入ガードなしのときにlang/src/runner/stage1_cli.hakoを子プロセスで起動する。STAGE1_EMIT_PROGRAM_JSON/STAGE1_EMIT_MIR_JSON/STAGE1_BACKEND/STAGE1_PROGRAM_JSONで mode 選択。entry override はSTAGE1_CLI_ENTRY/HAKORUNE_STAGE1_ENTRY。 - .hako 側:
Stage1Cliに最低限の本体を実装。emit_program_json: Stage‑1 UsingResolver で prefix を結合し、BuildBox.emit_program_json_v0 で Program(JSON v0) を返す。emit_mir_json:MirBuilderBox.emit_from_program_json_v0をそのまま呼ぶ(delegate 未設定なら null)。run_program_json: backend==llvm の場合はenv.codegen.emit_objectまで通す。vm/pyvm は当面 MIR(JSON) を stdout に出すのみ(実行は Stage0 橋渡し未配線)。- CLI:
emit program-json|mir-json/run --backend ... <src>を受理。NYASH_SCRIPT_ARGS_JSONを JSON で best-effort 伝播。
- Docs:
docs/private/roadmap2/phases/phase-25.1/stage1-usingresolver-loopform.mdに stub 状態を追記(run は暫定挙動)。 - Known gaps: vm/pyvm 実行はまだ Stage0 への橋渡し未着手。llvm も emit object 止まり(link/exec は後続)。
1-5. Phase 25.1A‑4 — Using SSOT 薄設計+BuildBox include 除去
- SSOT:
lang/src/using/resolve_ssot_box.hakoに README 相当のコメントと I/F(resolve_modules/resolve_prefix)を追加。現状は no-op だが「using をここで扱う」窓口を固定。 - Stage1UsingResolver:
resolve_for_program_jsonを追加し、SSOT を呼ぶ導線だけ確保(現状は透過返し)。 - BuildBox: 先頭の
includeを削除し、using lang.compiler.entry.bundle_resolver as BundleResolverに置換(Stage‑B パーサが include を解せない問題の足場づくり)。 - 実行確認:
NYASH_ALLOW_NYASH=1 HAKO_ALLOW_NYASH=1 NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ... cargo run --bin hakorune -- basic_test.hakoが RC=0 で完走(ただし stdout はRC: 0のみ、program-json 出力は未配線)。cargo run ... emit program-jsonは Rust CLI 側の表面が未対応のためエラー(想定挙動)。
2. まだ残っている問題・課題(2025-11-18 時点)
2-1. Stage‑B 本体の型エラー(String > Integer(13))
- 症状:
- Stage‑B の
Main.main実行時に、Type error: unsupported compare Gt on String(...) and Integer(13)が発生。 - LoopForm v2 / PHI / continue 修正とは 独立の Stage‑B 固有のロジック問題。
- Stage‑B の
- 想定される原因:
- Stage‑B 側の body 構築 / JSON 生成で、本来数値比較にすべき箇所で「生の文字列」と整数を比較している。
- もしくは、Parser/Scanner が
lenやインデックスを文字列のまま扱っている部分がある。
- 対応方針(次フェーズ向けメモ):
StageBBodyExtractorBox.build_body_srcが吐くbody_srcを最小ケースで抽出し、 その中から問題の比較式(>)がどのように生成されているかを特定する。- Stage‑B の box レベルで:
- 「どのフェーズで型を決めるか」(例: ParserBox / Stage‑B / VM 手前)を決めてから修正する。
- このタスクは 25.1c 続き or 新フェーズ(25.1n 相当)として、Stage‑B 箱の設計側で扱う。
2-2. Stage‑B 再入ガード env.set/2 の扱い
- 現状:
- 一部 Stage‑B コードが
env.set/2を使った再入ガードに依存しており、 Rust VM 側にはenv.set/2extern が定義されていないため、❌ VM error: Invalid instruction: extern function: Unknown: env.set/2が発生するケースがある。
- 一部 Stage‑B コードが
- 方針メモ:
- 選択肢 A: Stage‑B 再入ガードを Box 内 state(フィールド)に寄せて、
env.set/2依存をなくす。 - 選択肢 B: Stage‑B ハーネス専用に、最小限の
env.set/2extern を Rust 側に実装する(本番経路では使わない)。 - 25.1m では構造修正(Loop/PHI/receiver)を優先し、この extern の話は据え置き。
- 選択肢 A: Stage‑B 再入ガードを Box 内 state(フィールド)に寄せて、
2-3. .hako 側 LoopSSA v2(Rust LoopForm v2 との乖離)
- 現状:
lang/src/compiler/builder/ssa/loopssa.hakoのstabilize_merges()はまだ実質的に stub に近い。- Stage‑B Test2(
compiler_stageb.hako経由)では、Rust MIR 側で LoopForm v2 / PHI v2 が安定した後も、.hako側 LoopSSA 経由の JSON から生成した MIR で PHI/SSA 問題が残る可能性がある。
- 目標:
- Rust LoopForm v2 を「制御構造と PHI の SSOT」とみなし、
.hako側 LoopSSA が同じ Carrier/Pinned / preheader/header/exit の規約を JSON レベルで再現する。
- Rust LoopForm v2 を「制御構造と PHI の SSOT」とみなし、
- やること(フェーズ 25.1k 後半〜 25.1f/g 連携):
- 特定の関数(例:
BreakFinderBox._find_loops/2)を対象に、Rust 側 / .hako 側のそれぞれで生成されるループ構造を比較。 - LoopSSA v2 の中に:
- Carrier 変数の検出、
- pinned 変数の扱い、
- exit PHI の構築 を Rust LoopForm v2 に合わせて実装。
- 2025-11-19 追記:
BreakFinderBox._find_loops/2については、まず .hako 側を「region box」的に整理した。header_pos/header_id/exit_pos/exit_idまわりの異常系をcontinueではなくnext_iローカルへの代入で表現し、1 イテレーションの末尾でi = next_iに合流させる形に変更。- これにより、LoopForm v2 / LoopSSA 側から見ると「単一 region 内での分岐+最後に合流」という構造になり、 carrier/pinned 検出や今後の SSA 解析が行いやすくなった(挙動は従来と同じ)。
- 特定の関数(例:
2-5. static box / me セマンティクス(観測タスクへ移行)
- 現状:
static box StringHelpersのようなユーティリティ箱で、me.starts_with(src, i, kw)のように 同一箱内のヘルパー(starts_with)をme.経由で呼んでいたため、Stage‑3 降下時に引数ずれが発生していた。- 具体的には、
StringHelpers.starts_with_kw/3→StringHelpers.starts_with/3の降下で 実際の呼び出しがstarts_with("StringHelpers", src, i, kw)のような 4 引数形になり、starts_with(src, i, pat)側ではsrc="StringHelpers"/i=<ソース全文>となって、if i + m > nがString > Integer(13)比較に化けていた。
- 対応(完了済み・局所修正):
lang/src/shared/common/string_helpers.hakoのstarts_with_kwを、if me.starts_with(src, i, kw) == 0からif starts_with(src, i, kw) == 0に書き換え、 static box ユーティリティに対するme依存を除去した。- これにより、
starts_with内でのガード比較i + m > nはすべて整数同士となり、 Stage‑B fib ケースで発生していたString("...") > Integer(13)の TypeError は解消済み。
- 今後(Phase 25.1p 以降):
- static box 全般における
meセマンティクス(本当に「シングルトンインスタンス」として扱う箱と、 純粋な名前空間箱をどう区別するか)は、25.1p の DebugLog フェーズで観測しながら設計を詰める。 - 実際に Rust 層(
build_me_expression/lower_static_method_as_function/FunctionDefBuilder::is_instance_method)を 統一規約に寄せる作業は、25.1p 以降のサブタスクとして扱う(現時点では局所修正でバグのみ解消)。
- static box 全般における
2-4. Builder / Selfhost まわりの残タスク(超ざっくり)
- Builder 内部ルート(20.43 系):
MirBuilderBox経由の internal ルートで、MIR を stdout 経由ではなく一時ファイル / FileBox に書き出し、 ハーネスがそこから読む形に揃える案が残タスク。
- Selfhost CLI / Stage1 CLI:
- Stage‑B / Stage‑1 CLI を「Rust VM / LLVM / PyVM / selfhost」の 4 経路で安定確認するラインは進行中。
- 25.1m では Rust VM + Stage‑B balanced scan を優先し、CLI 全体は次のフェーズで詰める。
3. 次にやること(候補タスク)
ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。
Rust 側は LoopForm v2 / Stage‑B fib / Stage‑1 UsingResolver 構造テストまで一通り整ったので、当面は Stage‑1 CLI / selfhost ラインの小さな箱から進める。
A. Stage‑1 CLI / Stage0 ブリッジ(Phase 25.1 — いまここ)
- A‑1: Stage‑1 CLI stub を Stage0 Rust ランナーから呼び出すブリッジ(DONE)
- 実装:
src/runner/stage1_bridge.rs+src/runner/mod.rsにmaybe_run_stage1_cli_stubを追加。NYASH_USE_STAGE1_CLI=1(既定 OFF)かつNYASH_STAGE1_CLI_CHILD!=1のときにだけ有効。- entry
.hakoはSTAGE1_CLI_ENTRY/HAKORUNE_STAGE1_ENTRYで上書き可能(既定:lang/src/runner/stage1_cli.hako)。 - 入力ファイル / モード:
STAGE1_EMIT_PROGRAM_JSON=1:hakorune emit program-json <source.hako>を子プロセスで実行。STAGE1_EMIT_MIR_JSON=1:STAGE1_PROGRAM_JSONがあれば--from-program-json、なければ<source.hako>からemit mir-json。- 上記どちらも無い場合:
hakorune run --backend <backend> <source.hako>(backend はSTAGE1_BACKENDか CLI の backend 設定)。
NYASH_SCRIPT_ARGS_JSONを拾って--以降の script 引数も Stage‑1 側に転送。- 再入防止として子プロセスには
NYASH_STAGE1_CLI_CHILD=1を付与(子側からは Rust ブリッジを素通り)。
- 実装:
- A‑2: Stage‑1 CLI skeleton の責務を doc に固定(DONE)
lang/src/runner/stage1_cli.hakoでStage1Cli.emit_program_json/emit_mir_json/run_program_json/stage1_mainのシグネチャとトグルトポロジーを固定。docs/private/roadmap2/phases/phase-25.1/stage1-usingresolver-loopform.mdに Rust 側ブリッジの振る舞いとトグル名(NYASH_USE_STAGE1_CLI/STAGE1_EMIT_*/STAGE1_BACKEND/NYASH_STAGE1_CLI_CHILD)を追記。
- A‑3: 次ステップ(未着手)
- Stage‑1 CLI skeleton に Stage‑B/BuildBox/MirBuilder 呼び出しを順に実装し、「Program(JSON)/MIR(JSON) を selfhost 経由で emit できる」状態まで持っていく。
tools/selfhost/run_stage1_cli.shから呼び出す selfhost パスと、Rust CLl からのブリッジパスの両方で JSON I/O 契約が同じになるように揃える。
B. Stage‑1 UsingResolver / LoopForm v2 ライン(Phase 25.1e 系フォロー)
- B‑1: entry UsingResolver の Region+next_i 化とコメント整備(おおむね DONE)
lang/src/compiler/entry/using_resolver_box.hakoの 3 ループ(entries / JSON スキャン / modules_list)は Region+next_i 形に揃え済み。- 役割コメント:
resolve_for_source(Stage‑B body_src を受けて prefix を返す)、_collect_using_entries(JSON スキャンして entries を集める)、_build_module_map(modules_list を map 化)を明記済み。 HAKO_STAGEB_APPLY_USINGS=0の時は prefix を空 string にしつつ、depth ガードだけは走らせる仕様もコメントで固定。
- B‑2: UsingResolver 構造テスト(ループ形/PHI)の拡充(DONE)
src/tests/mir_stage1_using_resolver_verify.rsに Region+next_i ループと early-exit JSON スキャンパターンの軽量テストを追加。- これらは cargo test 経路で常時緑を維持し、v2 quick スモークへの昇格は「実行時間とノイズを見ながら後続フェーズで検討」という扱いにする(docs にメモ済み)。
- B‑3: pipeline_v2 UsingResolver との責務境界(DONE)
lang/src/compiler/pipeline_v2/using_resolver_box.hako冒頭に、「entry 側=ファイル I/O + using 収集」「pipeline_v2 側=modules_json 上の alias/path 解決」と役割メモを追加。- RegexFlow ベースの単一路ループは Region 化不要(stateful helper)とし、LoopForm v2 の観点からも「観測対象外」とする。
C. FuncScanner / Exit PHI カナリア(Phase 26-F / 26-G への橋渡し)
- C‑1: FuncScanner trim/skip_ws/parse_params の最小再現ケース固定(DONE)
lang/src/compiler/tests/funcscanner_trim_min.hakoで_trim/trim/skip_whitespaceを 1 回ずつ呼ぶ最小 Main を定義。src/tests/mir_funcscanner_trim_min.rsで:- Stage‑3 / using 有効化したパーサ設定で func_scanner.hako + 上記テストを一体コンパイル。
MirVerifierでモジュール全体の SSA/PHI を検証(HAKO_MIR_BUILDER_METHODIZE=0でも常に緑になることを確認)。- VM 実行は
NYASH_TRIM_MIN_VM=1のときだけ有効化(いまは MIR 側の根治が主目的)。
- C‑2: FuncScanner 側ロジックの構造整理(DONE)
lang/src/compiler/entry/func_scanner.hako:parse_paramsを Region+next_i 形の 1 本ループに整理し、先頭スキップとカンマ探索をそれぞれ helper に寄せた。trimは先頭側をskip_whitespaceに全面委譲し、末尾側のみ後ろ向きループで処理するように簡素化。skip_whitespaceはloop(i < n)+ if/continue だけにした最小形にし、過去の dev 向けログや loop(1==1) ワークアラウンドを撤去。
- これにより FuncScanner フロントは LoopForm v2 / LoopSSA v2 から見て「素直なループ+明確な next_i 形」になり、以後の PHI/ExitLiveness 側の根治作業が
.hakoに依存しづらい形になった。
- C‑3: Phase 26-F / 26-G のカナリアとして位置付け(進行中)
- 26-F 時点では
NYASH_EXIT_LIVE_ENABLE既定 OFF で、従来挙動のままmir_funcscanner_trim_minが MIR verify 緑になることを確認済み。 - 26-G では:
NYASH_EXIT_LIVE_ENABLE=1+ MirScanExitLiveness 経由でもmir_funcscanner_trim_min(特に FuncScannerBox.trim/1 / skip_whitespace/2 / parse_params/1)が常に緑になることを受け入れ条件にする。- そのうえで
mir_funcscanner_skip_ws_direct_vm/ Stage‑B / Stage‑1 UsingResolver 系のカナリアも順に緑に揃える。
- 26-F 時点では
D. Stage‑B / LoopSSA / Selfhost まわり(中期タスク)
- D‑1: Stage‑B 再入ガード
env.set/2の整理(据え置き)- dev 専用 extern を Rust 側に追加するか、Stage‑B 箱側で state に寄せるかを決める必要があるが、現在はループ/PHI ラインを優先し保留。
4. Phase 26-H — JoinIR 設計 & ミニ実験(新規フェーズ)
目的
- 制御構造(if / loop / break / continue / return)を 関数呼び出し+継続 に正規化する中間層(JoinIR)を設計し、LoopForm v2 / PHI / ExitLiveness の負担を将来軽くする足場を作る。
- 25.1 / 26-F / 26-G の本線を止めずに、「設計+ごく小さな実験」だけを先に進める。
- スモーク/本線は既存の MIR/LoopForm 経路のまま維持しつつ、徐々に「関数型(LoopFnIR/JoinIR)」側に重心を移す。
進捗(26-H 完了分)
- JoinIR 設計ドキュメント反映済み(
docs/development/architecture/join-ir.md) - 26-H README/TASKS でスコープ・最終箱セット・次フェーズ境界を明記
src/mir/join_ir.rsで JoinIR 型定義+ JoinIrMin 用のミニ自動変換を実装apps/tests/joinir_min_loop.hako+src/tests/mir_joinir_min.rs(トグル付きカナリア)を追加- トグル:
NYASH_JOINIR_EXPERIMENT=1で JoinIR 実験を有効化(デフォルトは既存 MIR/LoopForm のみ)
次フェーズ(Phase 27 — JoinIR 実用化)
- フォルダ:
docs/private/roadmap2/phases/phase-27-joinir/ - 27.1: JoinIR 変換を FuncScanner/Stage‑B の代表ループに拡張(トグル付き)
- 27.2: JoinIR → VM/LLVM ブリッジのプロトタイプを作り、A/B 実行を試す(トグル付き)
- 27.3: レガシー PHI/Loop 箱を段階削減(Header/Exit/LoopPhi 系の吸収・削除計画を実行)
やること(26-H スコープ)
-
H‑1: JoinIR 設計ドキュメントの追加(完了)
docs/development/architecture/join-ir.mdに命令セットと変換規則、対応表を記述済み。docs/private/roadmap2/phases/phase-26-H/README.mdに、26-H のスコープ/やらないこと/他フェーズとの関係を記載済み。
-
H‑2: JoinIR 型定義とミニ変換の骨格(完了)
src/mir/join_ir.rsにJoinFunction/JoinInst/JoinContId/JoinModule等の型を定義済み。lower_min_loop_to_joinirでJoinIrMin.main/0用の試験的な自動変換を実装(Phase 27.x で一般化予定)。src/tests/mir_joinir_min.rsとapps/tests/joinir_min_loop.hakoでカナリアテストを追加(NYASH_JOINIR_EXPERIMENT=1時のみ有効)。
-
H‑3: トグル付きミニ実験(完了)
NYASH_JOINIR_EXPERIMENT=1で JoinIR 実験テストを有効化。- トグル OFF 時は既存の MIR/LoopForm 経路のみが動作することを確認(ゼロリグレッション)。
やらないこと(26-H では保留)
- 既存の LoopForm v2 / PhiBuilderBox / ExitPhiBuilder を JoinIR ベースに全面移行すること。
- Stage‑B / Stage‑1 / CLI / selfhost ラインの本線を JoinIR で差し替えること。
- 既存の SSA/PHI 実装を削除すること(全部別フェーズで検討)。
優先度と位置付け
- 本線(いま重視する順):
- Phase 25.1 — Stage‑1 UsingResolver / Stage‑1 CLI program-json/mir-json を安定化。
- Phase 26-F / 26-G — Exit PHI / ExitLiveness の根治(LoopForm v2 / PHI SSOT / MirScanExitLiveness)。
- Phase 26-H は:
-
「本線の合間に進める設計フェーズ」として扱う。
-
JoinIR が小さいケースでうまく動くことを確認できたら、27.x 以降で本格的な導入を検討する。
-
このタスクに着手するときは「prod/CI 経路から完全に切り離した dev ガード」として設計する。
-
- C‑2: .hako LoopSSA v2 実装(Rust LoopForm v2 への追従)
- Rust LoopForm v2 の Carrier/Pinned/BodyLocalInOut モデルを
.hakoの LoopSSA に輸入し、Stage‑B Test2 / selfhost CLI でも MirVerifier 緑を目指す中〜長期タスク。 - 具体的な対象:
lang/src/compiler/builder/ssa/loopssa.hakoと Stage‑B/BreakFinder 周辺の region 化済みループ。
- Rust LoopForm v2 の Carrier/Pinned/BodyLocalInOut モデルを
- C‑3: Selfhost / CLI 周辺のテスト整理
- 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースを tests/tools 側でタグ付け(quick/integration/selfhost)、Phase 25.1 の「どこまでが Rust 側」「どこからが Stage1 側」を見える化する。
4. 履歴の見方メモ
- 以前の
CURRENT_TASK.mdは ~1900 行の長いログだったけど、読みやすさ重視でこのファイルはスナップショット形式にしたよ。 - 過去の詳細ログが必要になったら:
git log -p CURRENT_TASK.md- あるいは特定のコミット時点の
CURRENT_TASK.mdをgit show <commit>:CURRENT_TASK.md
でいつでも復元できるよ。
Phase 25.4 — Naming & Stage‑1 CLI Cleanup(design only)
- ねらい:
- static box / global 呼び出しの命名規約を NamingBox に集約し、VM 側のレガシーフォールバック経路を撤去する。
- Stage‑1 CLI の env/トグル解釈を 1 箇所の設定箱にまとめ、stage1_main の責務を薄く保つ。
__mir__.logベースの MIR ログ観測ポイントをドキュメントで一覧化し、将来の正式 API 化に備える。
- 現状:
- NamingBox(
src/mir/naming.rs)は導入済みで、Builder 側の static メソッド名はencode_static_method経由、VM 側の global 呼び出しはnormalize_static_global_name経由になっている。 - VM 側の「canonical 名で見つからなければ元名でもう一度探す」フォールバックは削除済みで、
mir_static_box_namingテスト群がMain._nop/0経路を固定している。 - Stage‑1 CLI は env-only 仕様(argv 依存なし)で Stage0 ブリッジと接続済みだが、env 群の解釈はまだ stage1_main 内に散在している。
- NamingBox(
- このフェーズでやること(設計レベル):
- NamingBox を「static 名に触るすべてのコードの SSOT」として整理(直接
format!("Box.main")する箇所を洗い出し)。 Stage1CliConfigBox(仮)を設計し、env→Config 変換の責務とフィールドを docs に書き出す(実装は後続でも可)。__mir__.logのタグと用途を 1 ページの docs にまとめ、dev 用ログと残したい観測ログを分けておく。
- NamingBox を「static 名に触るすべてのコードの SSOT」として整理(直接
以上が 2025-11-18 時点の Phase 21.8 / 25 / 25.1 / 25.2 / 25.4 ラインの「いまどこ」「なに済み」「なに残り」だよ。
次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。
3. これからやるタスクのラフ一覧(25.1 / Stage‑1 系)
ここから先は「まず設計と箱分割を書いてから実装」という方針で進めるタスク群だよ。
A. Rust 層解析(LoopForm v2 / JSON v0 / Stage‑1 観測)
- A-1: LoopForm v2 / LoopSnapshotMerge の入口確認
src/mir/loop_builder.rs/src/mir/phi_core/loopform_builder.rs/src/mir/phi_core/loop_snapshot_merge.rsを「Stage‑1 から見た導線」として読み直し、どのレイヤで Carrier / Pinned / BodyLocalInOut が決まるかを short メモ化する。
- A-2: JSON v0 → MIR ブリッジの導線整理
src/runner/json_v0_bridge/lowering/(特にloop_.rs)を通して、Program(JSON v0).body / defs.body(Block) が LoopForm v2 までどう運ばれるかを図として docs に落とす。
- A-3: Stage‑1 UsingResolver の MIR 観測
- 既存テスト
src/tests/mir_stage1_using_resolver_verify.rsの MIR dump を元に、「どのループが Region+next_i 化候補か」「既に問題なく LoopForm に乗っている場所はどこか」を箇条書きで整理する。
- 既存テスト
B. Stage‑1 UsingResolver 箱化・ループ整理(.hako 側)
- B-1: Stage1UsingResolverBox の責務分割設計
lang/src/compiler/entry/using_resolver_box.hakoを、collect_entries / modules_map / file_read などの小さいロジック箱に概念的に分割し、「どの箱が何を責務とするか」を docs に書く(実際のファイル分割は後段)。
- B-2: すべてのループを Region+next_i 形に揃える計画
- entry の UsingResolver 内に残っている「pos++/continue 多発型」ループを洗い出し、Region+next_i 形にどう書き換えるかを phase-25.1 docs に追記する。
- 目的: LoopForm v2 / PHI から見たときに「1 region / 1 backedge / 明確な次位置決定」という形に統一する。
- 状況メモ: entry 側 3 ループ(entries/JSON scan/modules_list)は Region+next_i 化済み。残りなし。
- B-3: pipeline_v2 UsingResolver との役割分担
lang/src/compiler/pipeline_v2/using_resolver_box.hakoと entry/Stage1UsingResolverBox のどちらが「テキスト / JSON / modules_map」のどこまでを担当するかを整理し、責務境界をドキュメントに固定する。- 状況メモ: pipeline_v2 側は modules_json の alias 解決のみ(RegexFlow.find_from の単一路ループ)。Region 化不要として据え置き、境界だけ明記。
- B-4: 構造テストの追加計画
- Region+next_i パターンの軽量 SSA テスト(すでに 1 本追加済み)を基準に、もう 1〜2 本、UsingResolver ソースに近いパターン(JSON スキャン+early-exit)を追加する方針を決める。
C. Stage‑1 CLI / program-json / mir-json Self‑Host 準備
- C-1: Stage‑1 CLI インターフェースの設計メモ
.hako → Program(JSON)/Program(JSON) → MIR(JSON)/MIR(JSON) → 実行を Stage‑1 側からどう呼び出すか(関数名・引数レベル)を docs に先に書く。
- C-2: Stage‑B → Stage‑1 データフロー図
compiler_stageb.hako→ Program(JSON v0) → Stage‑1 UsingResolver → MirBuilder までのパイプラインを Phase‑25.1 ドキュメントに 1 枚の図としてまとめる。
- C-3: Rust CLI 側ブリッジの最小ガード案
- Stage0/Rust CLI は「Program(JSON/MIR(JSON) を受け取り VM/LLVM に流すだけ」に縮退させる方針と、既定 OFF トグル(selfhost 入口)をどう切るかを設計メモとして追加。
D. 将来フェーズ向けメモ(variable_map 決定化ライン)
- D-1: MirBuilder::variable_map / BoxCompilationContext::variable_map BTreeMap 化案
- どの構造を HashMap→BTreeMap 化するか、どのテスト(
mir_funcscanner_skip_ws_vm_debug_flakyなど)で決定性を確認するかを Phase‑25.x の設計メモとして書いておく。
- どの構造を HashMap→BTreeMap 化するか、どのテスト(
- D-2: dev 専用 / flaky テストの扱い方針
- どのテストが dev ignore(手動実行用)で、いつ/何を満たしたら常時有効に戻すかを、このファイルと関連 README に明確に残す。
このセクションは「すぐ実装する TODO ではなく、25.1〜25.x ラインで踏むべき設計タスク一覧」として使う予定だよ。
(静的 me-call 修正の参考メモはここに残しつつ、別セクションは整理済み)
E. Legacy Loop/PHI 経路の囲い込みと削除準備
-
E-1: Legacy loop_phi.rs の役割と削除条件の明文化
- ファイル:
src/mir/phi_core/loop_phi.rs - 状態: LoopForm v2 + LoopSnapshotMergeBox への移行後は「legacy scaffold」としてのみ残存。
- やること:
- 現在の利用箇所を 2 種類に分類する:
- 本線経路(LoopForm v2 / Stage‑1 / Stage‑B から参照される部分)
- 互換レイヤ/解析専用(JSON v0 bridge, 旧 smokes, dev-only ヘルパ)
- 本線はすべて
loopform_builder.rs+header_phi_builder.rs+loop_snapshot_merge.rsで賄えることを確認し、loop_phi.rsを「legacy 専用(新規利用禁止)」として CURRENT_TASK と docs に固定しておく。 - Phase 31.x の cleanup で実ファイル削除してよい条件(参照 0+対応する smokes/テストの移行完了)を
docs/private/roadmap/phases/phase-31.2/legacy-loop-deletion-plan.md側と揃えておく。
- 現在の利用箇所を 2 種類に分類する:
- ファイル:
-
E-2: PHI/LoopForm 周辺で HashMap を使ってよい/いけない場所を線引き
- ファイル:
src/mir/builder.rs,src/mir/phi_core/*,src/mir/loop_builder.rs - やること:
- 「PHI 生成とスナップショット決定」に関わる構造では
BTreeMap/BTreeSet/Vec+sortのみに限定し、HashMapは使わない、というルールを Phase 25.1 docs に明記する。 - それ以外のメタ情報(plugin sigs, weak_fields など)は HashMap 維持可とし、「決定性」に影響しないことをコメントで示しておく。
- 代表として
loop_phi.rs内のsanitize_phi_inputsのように「一度 HashMap で集約してから sort する」パターンは、LoopForm v2 正系統ではPhiInputCollector+ BTree 系で代替されていることを確認し、legacy 側のみに閉じ込める。
- 「PHI 生成とスナップショット決定」に関わる構造では
- ファイル:
-
E-3: Legacy 経路の一覧と新規利用禁止ポリシーを docs に反映
- ファイル:
docs/private/roadmap2/phases/phase-25.1/README.md(本線側のルール)docs/private/roadmap/phases/phase-31.2/legacy-loop-deletion-plan.md(削除計画側と整合させる)
- やること:
- Legacy として扱うモジュール(例:
phi_core::loop_phi, 一部旧 JSON v0 bridge helper)を一覧にして、「新しいコードからここを呼ばない」「Phase 31.x で削除予定」と明記する。 - 逆に、今後 PHI/Loop/If で使うべき SSOT 箱(LoopForm v2 + HeaderPhiBuilder + BodyLocalPhiBuilder + if_phi + ControlForm)を 1 セクションで列挙し、「ここだけを見ると設計が分かる」導線を作る。
- Legacy として扱うモジュール(例:
- ファイル:
F. IfForm / Body-Local PHI 統合(Phase 26-F 進捗メモ)
- F-1: IfBodyLocalMergeBox の導入(完了)
- ファイル:
src/mir/phi_core/if_body_local_merge.rs - 責務: if-merge 専用の body-local PHI 候補決定。
- 両腕に存在する変数のみを候補とし、
pre_ifから値が変化した変数だけを返す。 - empty else の場合は空リストを返し、従来の PhiBuilderBox ロジックに委ねる。
- 両腕に存在する変数のみを候補とし、
- ファイル:
- F-2: LoopBuilder 内 if-merge への統合(進行中)
- ファイル:
src/mir/loop_builder.rs,src/mir/phi_core/phi_builder_box.rs - 状態: Phase 26-F-2 までで、loop 内 if の PHI 生成に IfBodyLocalMergeBox を噛ませるところまで実装。
- 代表テスト 1 本が新たに PASS になったが、Loop PHI 側に残る domination error(Value %48 / non-dominating use)がまだ存在。
- メモ: この残件は LoopForm Exit PHI 側(ExitPhiBuilder/LoopFormBuilder)の古い Case に起因する既知バグとして扱い、次フェーズで Loop PHI 側の SSOT 化(PhiBuilderBox への統合 or ExitPhiBuilder 根治)の入口とする。
- ファイル:
3. Phase 25.1q — LoopForm Front Unification(DONE / follow-up 別タスクへ)
- 目的: Rust AST ルート (LoopBuilder) と JSON v0 ルート (
json_v0_bridge::lower_loop_stmt) のループ lowering を “LoopFormBuilder + LoopSnapshotMergeBox” に一本化し、どの経路からでも同じ SSOT を見るだけで良い構造に揃える。 - 完了状態:
- AST ルート:
LoopBuilder::build_loop_with_loopformで canonicalcontinue_merge_bbを常時生成し、continue_targetを header ではなくcontinue_merge_bbに統一済み。LoopFormBuilder::seal_phis/LoopSnapshotMergeBoxの continue/exit 入力は「preheader + continue_merge + latch」「header + break snapshots」で完全管理。
- JSON v0 ルート:
loop_.rsでLoopFormJsonOpsを実装し、preheader/header/body/latch/continue_merge/exit の block ID を AST ルートと同じ形で生成。- break/continue/exit の snapshot を
LoopSnapshotMergeBoxでマージし、canonical continue_merge → header backedge を JSON 側でも採用。 tests/json_program_loop.rsで JSON v0 だけを入力にした軽量ループ(通常 / continue / body-local exit)をMirVerifierで確認するスモークを追加。
- Docs/README:
docs/private/roadmap2/phases/phase-25.1q/README.mdに「AST/JSON ともに LoopForm v2 + LoopSnapshotMergeBox が SSOT」と明記。src/runner/json_v0_bridge/README.mdで “bridge は薄いアダプタであり、新しい PHI 仕様は loopform 側でのみ扱う” とガードを追記。src/mir/phi_core/loop_phi.rsには “legacy(分析用のみ)” コメントを追加。将来の cleanup (Phase 31.x) で削除対象とする。
- AST ルート:
- 残タスクは別フェーズへ:
- Stage‑1 UsingResolver 周りの SSA バグ(
tests::mir_stage1_using_resolver_verify::mir_stage1_using_resolver_full_collect_entries_verifiesの Undefined Value)は 25.1q の範囲外。LoopForm の SSOT 化は終わっているため、今後は Stage‑1 側の PHI/Env スナップショット設計タスクとして切り出す。 - JSON v0 → Nyash AST への統合案や loop_phi.rs の実ファイル削除は、Phase 31.x cleanup 計画側で扱う。
- Stage‑1 UsingResolver 周りの SSA バグ(