Phase 25.1c/k: Fix ValueId undefined errors in loops with body-local variables **Problem:** - FuncScannerBox.scan_all_boxes/1 and BreakFinderBox._find_loops/2 had ValueId undefined errors for variables declared inside loop bodies - LoopFormBuilder only generated PHIs for preheader variables, missing body-locals - Example: `local ch = s.substring(i, i+1)` inside loop → undefined on next iteration **Solution:** 1. **Rust AST path** (src/mir/loop_builder.rs): - Detect body-local variables by comparing body_end_vars vs current_vars - Generate empty PHI nodes at loop header for body-local variables - Seal PHIs with latch + continue snapshot inputs after seal_phis() - Added HAKO_LOOP_PHI_TRACE=1 logging for debugging 2. **JSON v0 path** (already fixed in previous session): - src/runner/json_v0_bridge/lowering/loop_.rs handles body-locals - Uses same strategy but for JSON v0 bridge lowering **Results:** - ✅ FuncScannerBox.scan_all_boxes: 41 body-local PHIs generated - ✅ Main.main (demo harness): 23 body-local PHIs generated - ⚠️ Still some ValueId undefined errors remaining (exit PHI issue) **Files changed:** - src/mir/loop_builder.rs: body-local PHI generation logic - lang/src/compiler/entry/func_scanner.hako: debug logging - /tmp/stageb_funcscan_demo.hako: test harness 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
22 KiB
22 KiB
Current Task — Phase 21.8 / 25 / 25.1 / 25.2 Snapshot(2025-11-18 時点)
このファイルは「今どこまで終わっていて、次に何をやるか」を 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 を段階的に整える長期ライン。
- 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 規約に追いつかせる必要がある。
1. 最近完了した重要タスク
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 ロジックはそのままに、将来の置き換えポイントだけを明示している。
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. 次にやること(候補タスク)
ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。
-
Stage‑B / BreakFinder / FuncScanner ラインの SSA 根治(Phase 25.1m 続き)
- JSON v0 bridge の Loop lowering で、
backedge_to_condがJumpだけでなくBranch(cond→header/exit)も認識するように修正(loop_.rs)。
→ BreakFinderBox._find_loops/2 のような break を含むループでも header PHI が正しく張られるようにした。 - BreakFinderBox は解析用の static box として扱い、
me._find_loops/me._jumps_toをBreakFinderBox._find_loops/_jumps_toに正規化。
→me未定義による Undefined ValueId を避ける。 - Stage‑B → Stage‑1 パーサ経路は、
ParserBox.parse_program2のトップレベルループではなく、parse_block2でMain.mainの本文をブロックとしてパースし、
Program(JSON v0) を自前で組み立てる構造に変更(Stage‑B 固まり問題を回避)。
- JSON v0 bridge の Loop lowering で、
-
Stage‑B 再入ガード
env.set/2の整理- 再入ガードを Box 内 state で持つ方向か、Rust 側に dev 専用 extern を追加するかを決める。
- どちらにしても「本番経路(prod ランナー)には影響しない」ようフラグでガードする。
-
.hako LoopSSA v2 実装(LoopForm v2 への追従)
- Rust LoopForm v2 の規約(Carrier/Pinned / preheader/header/latch/exit)を
.hakoの LoopSSA に輸入。 - Stage‑B Test2 / selfhost CLI の JSON→MIR 経路で MirVerifier 緑を目標にする。
- Rust LoopForm v2 の規約(Carrier/Pinned / preheader/header/latch/exit)を
-
Selfhost / CLI 周辺のテスト整理
- 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースに対して、 「Phase 25.1m でどこまで緑になったか」「どこから先が 25.1k 以降の仕事か」を tests / tools 側で見える化する。
4. 履歴の見方メモ
- 以前の
CURRENT_TASK.mdは ~1900 行の長いログだったけど、読みやすさ重視でこのファイルはスナップショット形式にしたよ。 - 過去の詳細ログが必要になったら:
git log -p CURRENT_TASK.md- あるいは特定のコミット時点の
CURRENT_TASK.mdをgit show <commit>:CURRENT_TASK.md
でいつでも復元できるよ。
以上が 2025-11-18 時点の Phase 21.8 / 25 / 25.1 / 25.2 ラインの「いまどこ」「なに済み」「なに残り」だよ。
次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。
2-? Stage‑B FuncScanner (in progress)
- StageBFuncScannerBox を compiler_stageb.hako 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶように変更。
- VM 側の Undefined value (BreakFinderBox._find_loops/2, FuncScannerBox.scan_all_boxes/1) は解消済みで、Stage‑B 自体は rc=0 まで到達する。
- ただし現時点では StageBFuncScannerBox.scan_all_boxes(src) が fib サンプルに対しても defs=[] を返しており、Program(JSON) に TestBox.fib が載っていない状態。
- 一度 StageBDriverBox から直接 FuncScannerBox.scan_all_boxes を呼ぶ構成も試したが、この経路では FuncScannerBox.scan_all_boxes/1 で再び Undefined value が発生したため、現状は StageBFuncScannerBox 経由に戻している(Stage‑B rc は 0、defs は空)。
- StageBFuncScannerBox._find_matching_brace は FuncScannerBox._find_matching_brace と同じ balanced scan アルゴリズムに揃えつつ、
[funcscan/debug] _find_matching_brace enter open_idx=... n=...ログで挙動を観測できるようにしてある(close_idx=-1 になる原因調査用)。 - Rust Stage‑3 パーサ側の
usingパースを拡張し、using "lang.compiler.parser.box" as ParserBox形式を受理するようにしたうえで、FuncScannerBox 自身もこの形に正規化した。- これにより
lang/src/compiler/entry/func_scanner.hako単体に対して--dump-mirが通るようになり、FuncScannerBox.scan_all_boxes/1 の MIR を直接観測できる。
- これにより
- 次の担当者は StageBFuncScannerBox.scan_all_boxes/1 と FuncScannerBox.scan_all_boxes/1 の MIR を比較しつつ、Stage‑B body 抽出→FuncScanner→defs 注入の導線と LoopBuilder 側の exit PHI を突き合わせてほしい。
3. Static Box フィールド仕様ドキュメント(完了)
- docs/reference/language/LANGUAGE_REFERENCE_2025.md: 3.4 Static Boxパターンに「static box 内のフィールドはすべて static フィールドとして扱われる」旨を明記した。
- 言語仕様としては、static box の中では
PI: FloatBoxのような宣言で十分であり、追加のstaticキーワードは不要であることをドキュメント上で固定。 - 次タスク候補(Claude Code 向け): static box / box のフィールド挙動に関するサンプルとテスト(VM/JSON v0 両方)の整理、および static box 上の
meセマンティクス(25.1p DebugLog フェーズと連動)の仕様化。
3. Phase 25.1q — LoopForm Front Unification (planning)
- 目的: Rust AST ルート (LoopBuilder) と JSON v0 ルート (json_v0_bridge::lower_loop_stmt) のループ lowering フロントを整理し、PHI/LoopForm の SSOT を phi_core + LoopFormBuilder に明示的に寄せる。
- 現状:
- Rust AST → MIR は LoopBuilder + LoopFormBuilder で統一済み。
- JSON v0 → MIR は loop_.rs から同じ phi_core を呼んでいるが、ファイルが分かれており、LLM/人間が誤って JSON 側だけを触るケースがあった。
- 25.1q でやること(設計メモレベル):
- docs に「LoopForm/PHI の意味論は phi_core が SSOT」と明記。
- loop_.rs を「JSON から LoopForm に渡す薄いアダプタ」に限定し、余計なデバッグや独自分岐を増やさない方針を固定。
- 将来的に JSON v0 → AST → MirBuilder に寄せる統合案を設計メモとして整理(実装は 25.2 以降)。