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

109 lines
6.5 KiB
Markdown
Raw Normal View History

# Phase 25.1m — Static Method / VM Param Semantics Bugfix
Status: planningRust 側の暗黙レシーバ問題を切り出したバグ修正フェーズ)
## ゴール
- 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 時点)
- 再現(簡易例):
```hako
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 で扱っていた 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_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` のテストで確認する。
3. 実装方針(高レベル)
- `MirFunction::new`:
- 暗黙 receiver 判定を段階縮小し、最終的には「インスタンスメソッドのみ `MirType::Box(_)` を用いた明示的 receiver」に統一。
- 静的メソッド(`static box Foo { method bar(x){...} }`)は `signature.params == [Unknown]` のみを予約対象にし、
追加の `receiver_count` を持たない設計に戻す。
- `emit_unified_call`:
- Method call のみ receiver を args に足す (`args_local.insert(0, recv)`)、Global/static 呼び出しでは一切いじらない。
- `exec_function_inner`:
- 現状の「params[] と args[] を 1:1 でバインドする」実装を前提として保ち、
呼び出し規約の側MIR builder 側)で整合を取る。
## 非スコープ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
- 新規ユニットテスト:
- 簡易 TraceTest静的メソッド + 文字列引数)の MIR/VM 実行が `label=HELLO` となること。
- `mir_stage1_using_resolver_full_collect_entries_verifies` が LoopForm v2 経路で緑のまま(または改善)であること。
- 既存の StageB / selfhost / 数値系テスト:
- 25.1c までに整えた StageB / selfhost ラインの canaryfib defs / CLI runが、25.1m の変更で悪化していないこと。
- 挙動の安定:
- 静的メソッド呼び出しに関する「引数が null になる」「ValueId %0 が未定義になる」といった VM エラーが、新規テスト群で再現しないこと。