Files
nyash-codex 1747ec976c refactor(json_v0_bridge): Phase 25.1p - FunctionDefBuilder箱化+me予約修正
【変更内容】
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>
2025-11-19 10:08:04 +09:00
..

Phase 25.1m — Static Method / VM Param Semantics Bugfix

Status: completed静的メソッド / LoopForm v2 continue + PHI の根治完了)

ゴール

  • Rust MIR/VM 層に残っている「静的メソッド呼び出し時の引数ずれ/暗黙レシーバ」問題を解消し、
    StageB / Stage1 / 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() = 1label)でも total_value_ids = 2 を予約して params = [%0, %1] を組み立てている。
    • VM 側の exec_function_innerargs をそのまま func.params に 1:1 でバインドするため:
      • args = ["HELLO"]
      • %0"HELLO"(暗黙 receiver
      • %1Void(足りない分が Void 埋め) → label に null が入る。
    • その結果:
      • 静的メソッドに文字列リテラルを直接渡すと label が null 化される。
      • 25.1d/e で扱っていた Stage1 UsingResolver 系テスト(collect_entries/1)でも、
        %0 / %1 の扱いに由来する SSA 破綻が見えていた(現在は LoopForm v2/Conservative PHI 側で多くを解消済みだが、根底の呼び出し規約はまだ歪なまま)。

スコープ25.1m でやったこと)

  1. 呼び出し規約の 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」としているので、なるべく触らない
  2. 静的メソッドまわりの SSA/テストの洗い出し

    • 代表ケース:
      • src/tests/mir_stage1_using_resolver_verify.rs 内の
        mir_stage1_using_resolver_full_collect_entries_verifiesStage1UsingResolverFull.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 のテストで確認する。
  3. 実装方針(高レベル・結果)

    • MirFunction::new(静的メソッド / 暗黙レシーバ):
      • 暗黙 receiver 判定を是正し、Box 型の第 1 パラメータを持つ関数だけを「インスタンスメソッド with receiver」と見なすようにした。
      • 非 Box 型(String, Integer など)で始まるパラメータ列を持つ関数は、暗黙の receiver なしの静的メソッド / Global 関数として扱い、
        signature.params.len() と予約 ValueId 数が 1:1 になるように整理した。
    • build_static_main_boxMain.main(args) の扱い):
      • src/mir/builder/decls.rs にて、Main.main(args) を静的エントリとして MIR 化する経路を
        NYASH_BUILD_STATIC_MAIN_ENTRY=1 時のみ有効にし、通常の VM 実行では wrapper main() を正規エントリとするように変更した。
    • 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) 自体は変更しない。
  • StageB / Stage1 CLI の構造タスク:
    • StageB body 抽出bundle/usingRegionBox 観測は 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:
    • StageB / Stage1 CLI 側の構造デバッグRegionBox 的な観測、StageBArgs/BodyExtractor/Driver 分解)に専念。
    • StageBTraceBox は既にインスタンス box 化しており、静的メソッドのバグを踏まないようにしてある。
    • 25.1m で静的メソッド呼び出し規約が直れば、将来的に Trace 系 Box を static 化することも再検討できる。

受け入れ結果25.1m 実績)

  • 静的メソッド / 暗黙レシーバ:
    • StageB の StageBTraceBox.log(label) 呼び出しで、"StageBArgsBox.resolve_src:enter" 等のラベルが null 化されずに正しく渡る ことを確認。
    • Main.main(args) ループ未実行バグは、静的エントリ生成を env フラグ付きに限定し、wrapper main() を正規エントリとしたことで解消。
  • LoopForm v2 / continue + PHI:
    • 開発用 Hakoloop_continue_fixed.hako)で RC=3 + PHI エラーなしを確認。
    • StageB 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 ベースで確認。

25.1m 完了後に残っている課題(次フェーズ向けメモ)

  • StageB 本体:
    • Main.main 処理内で String(...) > Integer(13) のような異種型比較に起因する型エラーが残っているcontinue/PHI 修正とは独立)。
    • これは StageB の JSON 生成 / body_src 構造に属する問題のため、25.1m では扱わず、25.1c 続き or 次フェーズで箱単位に切り出して対応する。
    • Stage1 / StageB の JSON v0 defs については、25.1m で src/runner/json_v0_bridge/lowering.rs を調整し、
      • box_name != "Main" の関数定義をインスタンスメソッドとして扱い、
      • Bridge 側で暗黙 receiver me を先頭パラメータにバインドすることで、me._push_module_entry(...) のような呼び出し時に me が未定義にならないようにした。