【変更内容】 1. FunctionDefBuilder 箱化(SSOT化) - インスタンスメソッド判定の一元化 - パラメータ ValueId 生成の統一 - 変数マップ初期化の統一 2. ValueId(0) me 予約バグ修正 - is_instance_method() で box_name != "Main" 判定 - インスタンスメソッドは me を ValueId(0) に予約 - variable_map["me"] = ValueId(0) を自動設定 3. コード削減・可読性向上 - 60行 → 40行(関数定義処理) - 重複ロジック削除 - デバッグログ追加(is_instance表示) 【効果】 - json_v0_bridge 経路の ValueId(0) 未定義エラー解消 - Stage-B compiler で static box メソッドが正しく動作 - 設計の一貫性向上(me の扱いが明確) 【非スコープ】 - Rust MirBuilder 側は未修正(Phase 26で統一予定) - lower_static_method_as_function は現状維持 関連: Phase 25.1m (静的メソッド修正), Phase 25.1c/k (SSA修正) 🐱 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Phase 25.1m — Static Method / VM Param Semantics Bugfix
Status: completed(静的メソッド / LoopForm v2 continue + PHI の根治完了)
ゴール
- Rust MIR/VM 層に残っている「静的メソッド呼び出し時の引数ずれ/暗黙レシーバ」問題を解消し、
Stage‑B / Stage‑1 / selfhost / Dev トレース(TraceBox 系)がすべて 同じ呼び出し規約で動くようにする。 - 具体的には:
static box Foo { method bar(x){...} }に対して- 呼び出し:
Foo.bar("HELLO") - VM 内部:
params.len() == 1、args 1 本 →barの唯一の引数に"HELLO"が入る
- 呼び出し:
- 「暗黙の receiver(仮想 me)」を静的メソッドにだけ特別扱いしない設計に戻す。
実際に起きていた症状(2025-11-18 時点)
- 再現(簡易例):
static box TraceTest { method log(label){ if label == null { print("label=NULL") } else { print("label=\"\" + label") } } } static box Main { method main(args){ TraceTest.log("HELLO") return 0 } }- 期待:
label=HELLO - 実際:
label=NULL
- 期待:
- 原因の一次切り分け:
MirFunction::newが「名前に '.' を含み、かつ第 1 パラメータ型が Box でない関数」を「静的メソッド with 暗黙 receiver」とみなし、signature.params.len() = 1(label)でもtotal_value_ids = 2を予約してparams = [%0, %1]を組み立てている。
- VM 側の
exec_function_innerはargsをそのままfunc.paramsに 1:1 でバインドするため:args = ["HELLO"]%0←"HELLO"(暗黙 receiver)%1←Void(足りない分が Void 埋め) →labelに null が入る。
- その結果:
- 静的メソッドに文字列リテラルを直接渡すと label が null 化される。
- 25.1d/e で扱っていた Stage‑1 UsingResolver 系テスト(
collect_entries/1)でも、
%0/%1の扱いに由来する SSA 破綻が見えていた(現在は LoopForm v2/Conservative PHI 側で多くを解消済みだが、根底の呼び出し規約はまだ歪なまま)。
スコープ(25.1m でやったこと)
-
呼び出し規約の SSOT を決める
- 原則:
- インスタンスメソッド:
prepare_method_signature側でmeを明示的に第 1 パラメータに含める。 - 静的メソッド / Global 関数:
signature.paramsは「実引数と 1:1」のみ。暗黙レシーバを追加しない。
- インスタンスメソッド:
- 影響範囲の調査:
MirFunction::newの param reservation ロジック(暗黙 receiver 判定とtotal_value_ids計算)。emit_unified_call/CalleeResolverBoxの Method/Global 判定と receiver 差し込み。- VM 側
exec_function_innerの args バインド(ここは既に「params と args を 1:1」としているので、なるべく触らない)。
- 原則:
-
静的メソッドまわりの SSA/テストの洗い出し
- 代表ケース:
src/tests/mir_stage1_using_resolver_verify.rs内の
mir_stage1_using_resolver_full_collect_entries_verifies(Stage1UsingResolverFull.collect_entries/1を静的メソッドとして使うテスト)。- Dev 用トレース箱(今回の
StageBTraceBox/ 既存の TraceTest 相当)。
- 25.1m では:
- まず
trace_param_bug.hako相当のミニテスト(静的メソッド + 1 引数)を Rust 側にユニットテストとして追加し、
Bugfix 前後で「label に null が入らない」ことを固定する。 - 次に
Stage1UsingResolverFull.collect_entries/1を LoopForm v2 経路込みで通し、
%0/%1の ValueId 割り当てと PHI が健全であることをMirVerifierのテストで確認する。
- まず
- 代表ケース:
-
実装方針(高レベル・結果)
MirFunction::new(静的メソッド / 暗黙レシーバ):- 暗黙 receiver 判定を是正し、Box 型の第 1 パラメータを持つ関数だけを「インスタンスメソッド with receiver」と見なすようにした。
- 非 Box 型(
String,Integerなど)で始まるパラメータ列を持つ関数は、暗黙の receiver なしの静的メソッド / Global 関数として扱い、
signature.params.len()と予約 ValueId 数が 1:1 になるように整理した。
build_static_main_box(Main.main(args)の扱い):src/mir/builder/decls.rsにて、Main.main(args)を静的エントリとして MIR 化する経路を
NYASH_BUILD_STATIC_MAIN_ENTRY=1時のみ有効にし、通常の VM 実行では wrappermain()を正規エントリとするように変更した。
- LoopForm v2 / header PHI sealing:
src/mir/phi_core/loopform_builder.rs::LoopFormBuilder::seal_phisを拡張し、preheader + latch に加えて
continue_snapshotsからの入力も header PHI に統合するようにした。- これにより、balanced scan など「ループ本体で変数更新 →
continue→ 次イテレーション」のパターンでも、
header で参照される変数が preheader / continue / latch すべての predecessor から正しくマージされる。
- LoopBuilder から LoopForm への continue 橋渡し:
src/mir/loop_builder.rs::build_loop_with_loopformからself.continue_snapshots.clone()をseal_phisに渡し、
LoopBuilder が集めた continue 時点のスナップショットを LoopForm メタボックス側の PHI 入力に反映するようにした。
- ControlForm / LoopShape invariants:
src/mir/control_form.rs::LoopShape::debug_validateに以下の invariant を追加(debug ビルドのみ):continue_targetsの各ブロックからheaderへのエッジが存在すること。break_targetsの各ブロックからexitへのエッジが存在すること。
- これにより、continue / break 経路の CFG 破綻があれば構造レベルで早期に検知できる。
非スコープ(25.1m では扱わなかったこと)
- 言語仕様の変更:
- Hako/Nyash の静的メソッド構文 (
static box/method) 自体は変更しない。
- Hako/Nyash の静的メソッド構文 (
- Stage‑B / Stage‑1 CLI の構造タスク:
- Stage‑B body 抽出/bundle/using/RegionBox 観測は 25.1c のスコープに残す。
- VM 命令や Box 実装の追加:
- 25 フェーズのポリシーに従い、新しい命令・Box 機能は追加しない(既存の呼び出し規約を整えるだけ)。
関連フェーズとの関係と結果
- Phase 25.1d/e/g/k/l:
- Rust MIR 側の SSA/PHI(特に LoopForm v2 + Conservative PHI)と Region 観測レイヤは、静的メソッドを含む多くのケースで安定している。
- 25.1m はその上に残った「呼び出し規約レベルの歪み」を片付けるフェーズ。
- Phase 25.1c:
- Stage‑B / Stage‑1 CLI 側の構造デバッグ(RegionBox 的な観測、StageBArgs/BodyExtractor/Driver 分解)に専念。
- StageBTraceBox は既にインスタンス box 化しており、静的メソッドのバグを踏まないようにしてある。
- 25.1m で静的メソッド呼び出し規約が直れば、将来的に Trace 系 Box を static 化することも再検討できる。
受け入れ結果(25.1m 実績)
- 静的メソッド / 暗黙レシーバ:
- Stage‑B の
StageBTraceBox.log(label)呼び出しで、"StageBArgsBox.resolve_src:enter"等のラベルが null 化されずに正しく渡る ことを確認。 Main.main(args)ループ未実行バグは、静的エントリ生成を env フラグ付きに限定し、wrappermain()を正規エントリとしたことで解消。
- Stage‑B の
- LoopForm v2 / continue + PHI:
- 開発用 Hako(
loop_continue_fixed.hako)でRC=3+ PHI エラーなしを確認。 - Stage‑B balanced scan ループに 228 回のイテレーショントレースを付け、
ch == "{"ブランチ後のcontinueで正常に次イテレーションへ戻ることを Rust VM 実行で確認。 - 新規ユニットテスト
test_seal_phis_includes_continue_snapshotsと、既存のmir_stageb_loop_break_continue_verifies/mir_stage1_using_resolver_full_collect_entries_verifiesの両方が緑であることをcargo testベースで確認。
- 開発用 Hako(
25.1m 完了後に残っている課題(次フェーズ向けメモ)
- 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が未定義にならないようにした。