Files
hakorune/docs/development/roadmap/phases/phase-25.1p/README.md

188 lines
9.4 KiB
Markdown
Raw Normal View History

# 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 はそのための観測レイヤとして使う)。
2025-11-19 23:12:01 +09:00
### 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"]} ***!