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>
Phase 25.1p — MIR DebugLog 命令(構造設計メモ)
Status: planning(まだ実装しない。設計+用途整理だけ)
ねらい
- Rust / VM / LLVM すべての経路で共通に使える「MIR レベルのデバッグログ命令」を用意して、
- SSA ValueId の中身(どのブロックでどの値が入っているか)
- Loop/If ヘッダ時点のキャリア変数の状態
- BoxCall / MethodCall の receiver や args の実際の値 を簡単に観測できるようにする。
- 既存のポリシー:
- 仕様変更や挙動変更ではなく、「観測レイヤ(デバッグログ)」として追加する。
- 既定では完全に OFF。環境変数で opt-in したときだけログが出るようにする。
構造案(Rust 側)
1. MIR 命令の拡張
- ファイル:
src/mir/instruction.rs - 追加案:
pub enum MirInstruction {
// 既存の MIR 命令 ...
/// Debug logging instruction (dev-only)
/// 実行時にログ出力(VM/LLVM 共通の観測ポイント)
DebugLog {
message: String,
values: Vec<ValueId>, // ログに出したい SSA 値
},
}
- SSA/Verifier との整合:
DebugLogは 新しい値を定義しない(dst_value()は None)。used_values()はvaluesをそのまま返す。- これにより:
- SSA check(MultipleDefinition/UndefinedValue)は既存のロジックのままでよい。
- MergeUses/Dominator も「普通の値読み」として扱える。
2. VM 実行器での実装
- ファイル:
src/backend/mir_interpreter/exec.rs(または handlers 側) - 方針:
NYASH_MIR_DEBUG_LOG=1のときだけ効果を持つ。- それ以外のときは no-op(完全に挙動不変)。
- 擬似コード:
MirInstruction::DebugLog { message, values } => {
if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() != Some("1") {
continue;
}
eprint!("[MIR-LOG] {}", message);
for vid in values {
let val = self.regs.get(vid).cloned().unwrap_or(VMValue::Void);
eprint!(" %{} = {:?}", vid.0, val);
}
eprintln!();
}
3. Printer / Verifier での扱い
src/mir/printer.rs:- Debug 出力用に:
のように印字。
debug_log "msg" %1 %2 %3
- Debug 出力用に:
src/mir/verification/*:DebugLogは「副作用ありだが、新しい値は定義しない」命令として扱う。used_values()による UndefinedValue チェックの対象にはなるが、新しいエラー種別は不要。
Hako 側インターフェース案
1. 簡易マクロ(糖衣): __debug_log__
- 目的:
- Stage‑B / Stage‑1 / selfhost の .hako コードから簡単に DebugLog を差し込めるようにする。
- 例:
_build_module_map() {
local map = new MapBox()
__debug_log__("me before call", me)
__debug_log__("map before call", map)
me._push_module_entry(map, seg)
__debug_log__("map after call", map)
}
- 降下イメージ:
__debug_log__("msg", x, y)→MirInstruction::DebugLog { message: "msg".into(), values: [id_of(x), id_of(y)] }- json_v0_bridge / MirBuilder 双方から同じ命令を使えるようにする。
2. Bridge / LoopBuilder 側での自動挿入案(オプション)
これは「実装するかどうかは後で決める」拡張案として残しておく。
-
json_v0_bridge:
NYASH_AUTO_DEBUG_LOG=1のときだけ:- BoxCall や MethodCall の直前に
DebugLogを挿入し、receiver や主要変数をログ出力。
- BoxCall や MethodCall の直前に
- 例:
MirInstruction::DebugLog { message: format!("BoxCall recv at {}.{}()", box_name, method_name), values: vec![recv_id], }
-
LoopBuilder / LoopForm v2:
NYASH_LOOP_DEBUG_LOG=1のときだけ:- header ブロックの先頭に
DebugLogを挿入し、carrier 変数の値を 1 行でダンプ。
- header ブロックの先頭に
- 例:
MirInstruction::DebugLog { message: "Loop header carriers".to_string(), values: carrier_value_ids.clone(), }
フェーズ内タスク(まだ実装しないメモ)
- 設計固め
- MirInstruction への
DebugLog追加仕様を最終確定(used_values / dst_value の扱い)。 - Verifier への影響(特に MergeUses / RetBlockPurity)を整理(ログ命令を許可するポリシー)。
- MirInstruction への
- Rust 実装(最小)
src/mir/instruction.rsに DebugLog variant を追加。src/backend/mir_interpreter/exec.rsに dev-only 実装を追加(NYASH_MIR_DEBUG_LOG=1ガード)。src/mir/printer.rsに印字サポートを追加。
- Hako からの利用導線
- Hako パーサ / MirBuilder 側に
__debug_log__的な糖衣マクロを追加(構文をどうするかは別途検討)。 - json_v0_bridge / MirBuilder のどこで DebugLog を使うか「観測ポイント候補」を CURRENT_TASK 側にメモ。
- Hako パーサ / MirBuilder 側に
- 拡張(任意)
NYASH_AUTO_DEBUG_LOG=1/NYASH_LOOP_DEBUG_LOG=1などのデバッグ専用トグルを検討。- LLVM ライン(PyVM/llvmlite ハーネス)での対応方法(printf など)を検討。
5. build_me_expression / static box との関係(検証タスクに含める)
- 現状:
build_me_expression()は、variable_map["me"]があればそれを返し、なければConst String(me_tag)を生成してプレースホルダとして扱う実装になっている。- インスタンスメソッド(
lower_method_as_function経由)では params にmeが含まれるため、variable_map["me"]から正しい Box パラメータが返る。 - 一方で、static box / static 関数経路では
meが文字列プレースホルダになるケースがあり、言語仕様上の「静的Boxでも暗黙 self が存在する」規約とはズレがある。
- 25.1p でやること(設計+観測):
- DebugLog を使って、static box 内での
meの実際の ValueId/VMValue をログし、「どこで文字列プレースホルダが使われているか」を可視化する。 lower_static_method_as_functionとlower_method_as_functionの責務を比較し、 static box メソッドに対しても暗黙 receiver をパラメータとして扱うべきかどうかを設計レベルで判断する。- 必要であれば、別フェーズ(例: 25.1q)で「static box メソッドの me 取り扱い」を Box 理論ベースで揃える(DebugLog はそのための観測レイヤとして使う)。
- DebugLog を使って、static box 内での
6. Static box / me セマンティクス統一(部分完了メモ)
- 25.1c/25.1m までに判明したこと:
static box StringHelpersのような「純粋ユーティリティ箱」で、me.starts_with(src, i, kw)のように 同一箱内ヘルパーを receiver 経由で呼ぶと、Stage‑3 降下で引数ずれ(iにソース全文が入る)が発生しうる。- 実際、
StringHelpers.starts_with_kw/3→StringHelpers.starts_with/3経路でStringHelpers.starts_with("StringHelpers", src, i, kw)のような形になり、i + m > nがString > Integer(13)の比較に化けていた。
- 25.1m での暫定対応(完了済み):
StringHelpers.starts_with_kw内をme.starts_with(src, i, kw)ではなく、素のstarts_with(src, i, kw)呼び出しに変更し、 static box ユーティリティに対するme依存を排除した。- これにより、
starts_with内の比較は全て整数同士となり、String > Integer型エラーは解消済み。
- 25.1p 以降でやること:
- DebugLog を使って、static box 全般(
StringHelpers以外も含む)のmeの振る舞いを観測し、 「本当にインスタンスとして扱いたい static box」と「名前空間としての static box」を切り分ける。 - 必要に応じて、前述の
build_me_expression/lower_static_method_as_function/FunctionDefBuilder::is_instance_methodの SSOT 設計を詰め、「static box における me セマンティクス」を Rust 側に反映するタスクを別フェーズでまとめて行う。
- DebugLog を使って、static box 全般(
いつやるか(優先度メモ)
- 今回は フォルダ+設計メモだけ で、実装はまだ行わない。
- 実装タイミングの候補:
- Stage‑B / selfhost の SSA バグ(BreakFinderBox / ParserStringScanBox / Stage‑B Test2)がもう一段深いところで詰まったとき、 「DebugLog を先に入れて ValueId の中身を観測しながら直す」フェーズとして 25.1p を前倒しする。
- あるいは 25.1c/25.1k の大きな山が一段落してから、「開発者向けのデバッグ基盤」としてまとめて導入する。
このフェーズは「やることの設計だけ先に箱にしておく」位置づけだよ。
本当に必要になったタイミングで 25.1p をアクティブにして、ここに書いたタスクを少しずつ実装していく想定。***
End Patch ***!
I apologize, but I can't apply this patch because it isn't formatted correctly. Could you fix it and try again? ```
Testing code: ```md
# Phase 25.1p — MIR DebugLog 命令(構造設計メモ)
...
本当に必要になったタイミングで 25.1p をアクティブにして、ここに書いたタスクを少しずつ実装していく想定。*** -> ``` workdir=/home/tomoaki/git/hakorune-selfhost"]} ***!