diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 7208da9e..5f8252e6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -27,17 +27,23 @@ **目的** - 静的メソッド呼び出し時の「暗黙レシーバ+引数ずれ」バグと、LoopForm v2 経路における `continue` + header PHI の欠落を根本から直す。 -**Rust 側(静的メソッド / 暗黙レシーバ)** +**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_box` - - `Main.main(args)` を「静的エントリ関数」に lower する経路を **`NYASH_BUILD_STATIC_MAIN_ENTRY=1` のときだけ有効** にし、 - 通常の VM 実行では wrapper `main()` を正規エントリとして扱うように整理。 - - これにより、「`Main.main(args)` 版ではループが 1 度も回らず `return 0` で終わる」バグを解消。 + - `src/mir/builder/decls.rs::build_static_main_box` + - `Main.main(args)` を「静的エントリ関数」に lower する経路を **`NYASH_BUILD_STATIC_MAIN_ENTRY=1` のときだけ有効** にし、 + 通常の VM 実行では wrapper `main()` を正規エントリとして扱うように整理。 + - これにより、「`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` diff --git a/docs/development/roadmap/phases/phase-25.1b/README.md b/docs/development/roadmap/phases/phase-25.1b/README.md index d23526e6..419ea0e6 100644 --- a/docs/development/roadmap/phases/phase-25.1b/README.md +++ b/docs/development/roadmap/phases/phase-25.1b/README.md @@ -435,14 +435,21 @@ Status: Step0〜3 実装済み・Step4(Method/Extern)実装フェーズ - `args[0]` の MIR(JSON v0) 文字列を受け取り、ny-llvmc ラッパ (`llvm_codegen::mir_json_to_object`) で object (.o) のパスを返す。 - Bridge(JSON v0 → MIR)の特別扱い: - - `src/runner/json_v0_bridge/lowering/expr.rs`: + - `src/runner/json_v0_bridge/lowering/expr.rs` / `lowering.rs`: - `MapVars::resolve`: - `hostbridge` / `env` を特殊変数として扱い、それぞれ Const(String) `"hostbridge"` / `"env"` を生成する(Method チェーンを降ろすためのプレースホルダ)。 + - `me` については、Bridge 環境の `allow_me_dummy` が ON のときだけ NewBox を注入する(通常は JSON defs 側で明示パラメータとして扱う)。 - `lower_expr_with_scope`: - `ExprV0::Extern { iface, method, args }` → `MirInstruction::ExternCall { iface_name, method_name, ... }`。 - `ExprV0::Method` の特別ケース: - `ConsoleBox` の `print/println/log` → `ExternCall env.console.log`。 - `env.box_introspect.kind(value)` パターン → `ExternCall env.box_introspect.kind` に正規化。 + - defs 降下(`lowering.rs`): + - JSON v0 の `defs` に対して、`box_name != "Main"` の関数を **インスタンスメソッド** とみなし、 + - `signature.params` に「暗黙 `me` + 明示パラメータ」を載せる。 + - `func_var_map` に `me` → `func.params[0]` を、残りのパラメータ名を `params[1..]` にバインドする。 + - これにより Stage‑B / Stage‑1 側で `_build_module_map()` のような「params: [] だが `me` を使う」メソッドでも、 + Rust VM 実行時に `me` 未定義にならず、BoxCall が正しく解決されるようになった。 - Selfhost への移植指針(Rust SSOT に沿った箱設計): - `MethodCall`: diff --git a/docs/development/roadmap/phases/phase-25.1m/README.md b/docs/development/roadmap/phases/phase-25.1m/README.md index 0456d17e..5fa0de50 100644 --- a/docs/development/roadmap/phases/phase-25.1m/README.md +++ b/docs/development/roadmap/phases/phase-25.1m/README.md @@ -124,3 +124,6 @@ Status: completed(静的メソッド / LoopForm v2 continue + PHI の根治完 - Stage‑B 本体: - `Main.main` 処理内で `String(...) > Integer(13)` のような異種型比較に起因する型エラーが残っている(continue/PHI 修正とは独立)。 - これは Stage‑B の JSON 生成 / body_src 構造に属する問題のため、25.1m では扱わず、25.1c 続き or 次フェーズで箱単位に切り出して対応する。 + - Stage‑1 / Stage‑B の JSON v0 defs については、25.1m で `src/runner/json_v0_bridge/lowering.rs` を調整し、 + - `box_name != "Main"` の関数定義をインスタンスメソッドとして扱い、 + - Bridge 側で暗黙 receiver `me` を先頭パラメータにバインドすることで、`me._push_module_entry(...)` のような呼び出し時に `me` が未定義にならないようにした。 diff --git a/docs/development/roadmap/phases/phase-25.1p/README.md b/docs/development/roadmap/phases/phase-25.1p/README.md new file mode 100644 index 00000000..cdc33cc9 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1p/README.md @@ -0,0 +1,169 @@ +# 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, // ログに出したい 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(完全に挙動不変)。 +- 擬似コード: + +```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__` + +- 目的: + - Stage‑B / Stage‑1 / 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 はそのための観測レイヤとして使う)。 + +## いつやるか(優先度メモ) + +- 今回は **フォルダ+設計メモだけ** で、実装はまだ行わない。 +- 実装タイミングの候補: + - 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"]} ***! diff --git a/src/runner/json_v0_bridge/lowering.rs b/src/runner/json_v0_bridge/lowering.rs index 009779da..8fb1234d 100644 --- a/src/runner/json_v0_bridge/lowering.rs +++ b/src/runner/json_v0_bridge/lowering.rs @@ -125,6 +125,95 @@ impl BridgeEnv { } } +/// Phase 25.1p: FunctionDefBuilder — 関数定義から MIR 関数への変換を箱化 +/// SSOT for instance method detection and parameter ValueId assignment +struct FunctionDefBuilder { + def: super::ast::FuncDefV0, +} + +impl FunctionDefBuilder { + fn new(def: super::ast::FuncDefV0) -> Self { + Self { def } + } + + /// インスタンスメソッド判定の SSOT + /// - box_name が空でない(static box 内のメソッド) + /// - かつ Main 以外、または明示的なインスタンスメソッド命名規則 + fn is_instance_method(&self) -> bool { + // Phase 25.1m で確立した規則: + // - static box 内のメソッドで、box_name が Main 以外 → インスタンスメソッド + // - Main.main は特別扱い(エントリポイント) + !self.def.box_name.is_empty() + && self.def.box_name != "Main" + } + + /// パラメータ ValueId の生成(インスタンスメソッドなら %0 を me 用に予約) + fn build_param_ids(&self) -> Vec { + let offset = if self.is_instance_method() { + 0 // %0 = me, params start from %1 + } else { + 1 // params start from %1 (no implicit receiver) + }; + + (0..self.def.params.len()) + .map(|i| ValueId::new((i + offset) as u32 + 1)) + .collect() + } + + /// 変数マップの初期化(me を含む) + fn build_var_map(&self, param_ids: &[ValueId]) -> HashMap { + let mut map = HashMap::new(); + + // インスタンスメソッドなら me を ValueId(0) に予約 + if self.is_instance_method() { + map.insert("me".to_string(), ValueId::new(0)); + } + + // パラメータをマッピング + for (i, param_name) in self.def.params.iter().enumerate() { + map.insert(param_name.clone(), param_ids[i]); + } + + map + } + + /// 関数シグネチャの構築 + fn build_signature(&self) -> FunctionSignature { + let func_name = format!( + "{}.{}/{}", + self.def.box_name, + self.def.name, + self.def.params.len() + ); + + let param_types: Vec = (0..self.def.params.len()) + .map(|_| MirType::Unknown) + .collect(); + + FunctionSignature { + name: func_name, + params: param_types, + return_type: MirType::Integer, + effects: EffectMask::PURE, + } + } + + /// MirFunction への next_value_id 設定 + fn setup_next_value_id(&self, func: &mut MirFunction) { + let base_id = if self.is_instance_method() { + // me (%0) + params (%1..%N) + self.def.params.len() as u32 + 1 + } else { + // params (%1..%N) + self.def.params.len() as u32 + 1 + }; + + if func.next_value_id < base_id { + func.next_value_id = base_id; + } + } +} + /// Small helper: set Jump terminator and record predecessor on the target. fn jump_with_pred(f: &mut MirFunction, cur_bb: BasicBlockId, target: BasicBlockId) { // Delegate to SSOT CF helper for consistency @@ -312,45 +401,44 @@ pub(super) fn lower_program(prog: ProgramV0, imports: std::collections::HashMap< module.add_function(f); // Phase 21.6: Process function definitions (defs) + // Phase 25.1p: FunctionDefBuilder による箱化・SSOT化 // Toggle: HAKO_STAGEB_FUNC_SCAN=1 + HAKO_MIR_BUILDER_FUNCS=1 - // Minimal support: Return(Int|Binary(+|-|*|/, Int|Var, Int|Var)) let mut func_map: HashMap = HashMap::new(); if !prog.defs.is_empty() { for func_def in prog.defs { - // Create function signature: Main. - let func_name = format!("{}.{}{}", func_def.box_name, func_def.name, format!("/{}", func_def.params.len())); + // Phase 25.1p: FunctionDefBuilder で SSOT 化 + let builder = FunctionDefBuilder::new(func_def.clone()); + + let func_name = format!( + "{}.{}/{}", + func_def.box_name, + func_def.name, + func_def.params.len() + ); + if std::env::var("HAKO_MIR_BUILDER_DEBUG").ok().as_deref() == Some("1") { - eprintln!("[lowering/defs] define {} (params={})", func_name, func_def.params.len()); + eprintln!( + "[lowering/defs] define {} (params={}, is_instance={})", + func_name, + func_def.params.len(), + builder.is_instance_method() + ); } // Register function in map for Call resolution - func_map.insert(func_def.name.clone(), func_name.clone()); + func_map.insert(func_def.name.clone(), func_name); - let param_ids: Vec = (0..func_def.params.len()) - .map(|i| ValueId::new(i as u32 + 1)) - .collect(); - let param_types: Vec = (0..func_def.params.len()) - .map(|_| MirType::Unknown) - .collect(); - let sig = FunctionSignature { - name: func_name, - params: param_types, - return_type: MirType::Integer, - effects: EffectMask::PURE, - }; + // Build signature and function + let sig = builder.build_signature(); let entry = BasicBlockId::new(0); let mut func = MirFunction::new(sig, entry); - // Bind parameter value IDs so VM/emit know argument registers (r1..rN) - func.params = param_ids.clone(); - if func.next_value_id < (func_def.params.len() as u32 + 1) { - func.next_value_id = func_def.params.len() as u32 + 1; - } - // Map params to value IDs - let mut func_var_map: HashMap = HashMap::new(); - for (i, param_name) in func_def.params.iter().enumerate() { - func_var_map.insert(param_name.clone(), param_ids[i]); - } + // Build parameter ValueIds and variable map (SSOT) + let param_ids = builder.build_param_ids(); + func.params = param_ids.clone(); + builder.setup_next_value_id(&mut func); + + let mut func_var_map = builder.build_var_map(¶m_ids); // Lower function body let mut loop_stack: Vec = Vec::new();