Files
hakorune/docs/development/roadmap/phases/phase-25.1p/README.md
nyash-codex 525e59bc8d feat(loop-phi): Add body-local variable PHI generation for Rust AST loops
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>
2025-11-19 23:12:01 +09:00

188 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`
- 追加案:
```rust
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 checkMultipleDefinition/UndefinedValueは既存のロジックのままでよい。
- MergeUses/Dominator も「普通の値読み」として扱える。
### 2. VM 実行器での実装
- ファイル: `src/backend/mir_interpreter/exec.rs`(または handlers 側)
- 方針:
- `NYASH_MIR_DEBUG_LOG=1` のときだけ効果を持つ。
- それ以外のときは no-op完全に挙動不変
- 擬似コード:
```rust
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 出力用に:
```text
debug_log "msg" %1 %2 %3
```
のように印字。
- `src/mir/verification/*`:
- `DebugLog` は「副作用ありだが、新しい値は定義しない」命令として扱う。
- `used_values()` による UndefinedValue チェックの対象にはなるが、新しいエラー種別は不要。
## Hako 側インターフェース案
### 1. 簡易マクロ(糖衣): `__debug_log__`
- 目的:
- StageB / Stage1 / selfhost の .hako コードから簡単に DebugLog を差し込めるようにする。
- 例:
```hako
_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 や主要変数をログ出力。
- 例:
```rust
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 行でダンプ。
- 例:
```rust
MirInstruction::DebugLog {
message: "Loop header carriers".to_string(),
values: carrier_value_ids.clone(),
}
```
## フェーズ内タスク(まだ実装しないメモ)
1. 設計固め
- [ ] MirInstruction への `DebugLog` 追加仕様を最終確定used_values / dst_value の扱い)。
- [ ] Verifier への影響(特に MergeUses / RetBlockPurityを整理ログ命令を許可するポリシー
2. Rust 実装(最小)
- [ ] `src/mir/instruction.rs` に DebugLog variant を追加。
- [ ] `src/backend/mir_interpreter/exec.rs` に dev-only 実装を追加(`NYASH_MIR_DEBUG_LOG=1` ガード)。
- [ ] `src/mir/printer.rs` に印字サポートを追加。
3. Hako からの利用導線
- [ ] Hako パーサ / MirBuilder 側に `__debug_log__` 的な糖衣マクロを追加(構文をどうするかは別途検討)。
- [ ] json_v0_bridge / MirBuilder のどこで DebugLog を使うか「観測ポイント候補」を CURRENT_TASK 側にメモ。
4. 拡張(任意)
- [ ] `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 はそのための観測レイヤとして使う)。
### 6. Static box / me セマンティクス統一(部分完了メモ)
- 25.1c/25.1m までに判明したこと:
- `static box StringHelpers` のような「純粋ユーティリティ箱」で、`me.starts_with(src, i, kw)` のように
同一箱内ヘルパーを receiver 経由で呼ぶと、Stage3 降下で引数ずれ(`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 側に反映するタスクを別フェーズでまとめて行う。
## いつやるか(優先度メモ)
- 今回は **フォルダ+設計メモだけ** で、実装はまだ行わない。
- 実装タイミングの候補:
- StageB / selfhost の SSA バグBreakFinderBox / ParserStringScanBox / StageB 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"]} ***!