Files
hakorune/docs/development/roadmap/phases/phase-25.1b/README.md
nyash-codex 5f06d82ee5 Phase 25.1b: Step B完了(multi-carrier LoopForm対応)
Step B実装内容(fibonacci風マルチキャリアループ対応):
- LoopFormBox拡張:
  - multi_count mode追加(build2メソッド)
  - build_loop_multi_carrierメソッド実装(4-PHI, 5 blocks)
  - 3変数(i, a, b)同時追跡のfibonacci構造生成

- LowerLoopMultiCarrierBox新規実装:
  - 複数Local/Assign検出(2+変数)
  - キャリア変数抽出
  - mode="multi_count"でLoopOptsBox.build2呼び出し
  - Fail-Fast: insufficient_carriersタグ出力

- FuncBodyBasicLowerBox拡張:
  - _try_lower_loopに呼び出し導線追加
  - 優先順位: sum_bc → multi_carrier → simple
  - [funcs/basic:loop.multi_carrier]タグ出力

- Module export設定:
  - lang/src/mir/hako_module.toml: sum_bc/multi_carrier追加
  - nyash.toml: 対応するmodule path追加

既存mode完全保持(Rust Freeze遵守):
- count, sum_bcは一切変更なし
- multi_countは完全に独立して追加
- 既存テストへの影響ゼロ

Technical Details:
- PHI構造: 3-PHI (i, a, b) in Header
- Block構成: Preheader → Header → Body → Latch → Exit
- Fibonacci計算: t = a+b, a' = b, b' = t
- copy命令でLatchから Headerへ値を渡す

Task先生調査結果を反映:
- Rust層のパターンC(4-PHI, multi-carrier)に対応
- MirSchemaBox経由で型安全なMIR生成

Next: スモークテスト追加、既存テスト全通確認

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-16 03:11:49 +09:00

540 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive)
Status: Step0〜3 実装済み・Step4Method/Extern実装フェーズ
## ゴール
- Rust 側 Program→MIR (`env.mirbuilder.emit`) と Hakorune 側 selfhost builder (`MirBuilderBox.emit_from_program_json_v0`) の機能差を埋め、Stage1 CLIlauncher.hakoレベルの Program(JSON) を selfhost builder 単独で lowering できるようにする。
- `.hako → Program(JSON v0) → MIR(JSON)` のうち、「Program→MIR」を selfhost builder だけでも成立させ、provider 経路はあくまで退避路に下げていく。
- 最終的には `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻し、Stage1 CLI EXE の I/OJSON stdout + exit codeを Rust/llvmlite と同じ契約に揃える。
## 現状Phase 25.1a 時点)
### StageBProgram(JSON v0) emit
- `compiler_stageb.hako``defs` を含む Program(JSON v0) を出力できる:
- `HakoCli.run` / `HakoCli.cmd_emit_*` / `HakoCli.cmd_build_*` などのメソッドを `Program.defs` 配列として含む。
- `FuncScannerBox` `HAKO_STAGEB_FUNC_SCAN=1` により、`static box` メソッド(暗黙の `me` 引数付き)も defs に載る。
- using 解決:
- `Stage1UsingResolverBox``lang/src/compiler/entry/using_resolver_box.hako` `HAKO_STAGEB_MODULES_LIST``nyash.toml``[modules]` を参照し、`using lang.mir.builder.MirBuilderBox` 等をファイル結合前に解決。
- StageB entry 側は string literal using を廃止し、`using lang.compiler.entry.using_resolver as Stage1UsingResolverBox` のように module alias を使用する。
#### StageB の安定度と使用上の注意
- 正規経路:
- StageB は `tools/hakorune_emit_mir.sh` / `tools/selfhost/selfhost_build.sh` 経由で呼び出すことを前提としており、これらのラッパが Stage3 用 ENV`NYASH_PARSER_STAGE3=1` / `HAKO_PARSER_STAGE3=1` / `NYASH_PARSER_ALLOW_SEMICOLON=1` など)を一括でセットする。
- Phase 25.1b では「このラッパ経由で呼ぶ限り StageB 自体は安定」とみなし、主な改善対象を Program→MIR の selfhost builder 側に置く。
- 手動実行時の注意:
- Stage3 ENV を立てずに StageB / VM を直接叩くと、`Undefined variable: local` のようなエラーが発生するが、これは構文/実装バグではなく「Stage3 キーワードlocal など)を Stage1 と同じルールでパースしてしまっている」ため。
- 詳細な原因と対処は `docs/development/troubleshooting/stage3-local-keyword-guide.md` にまとめてあり、selfhost 開発では「まずラッパスクリプトを使う → 必要な場合のみ ENV を明示して直叩きする」方針とする。
### Rust provider (`env.mirbuilder.emit`)
- `program_json_to_mir_json_with_imports`:
- `Program.body``Program.defs` の両方を受理し、`FuncDefV0` から `func_map` を構築して Call を解決。
- `HAKO_MIRBUILDER_IMPORTS` 経由で `MirBuilderBox` / `BuildBox` などの static box 名をインポートし、`Const(String(alias))` として扱う。
- JSON v0 ブリッジ:
- `args` を暗黙パラメータとして扱う修正済み(`undefined variable: args` は解消)。
- `hostbridge` は wellknown 変数として `Const(String("hostbridge"))` を生成し、`hostbridge.extern_invoke(...)` を含む Program(JSON) でも undefined にならない。
- 結果:
- `launcher.hako` に対して ~62KB の MIR(JSON) を安定して生成できており、Phase 25.1a では provider 経路が事実上のメインルート。
### selfhost builder (`MirBuilderBox.emit_from_program_json_v0`)
- エントリ:
- 入口で `HAKO_MIR_BUILDER_FUNCS=1` のときに `FuncLoweringBox.lower_func_defs(program_json, program_json)` を呼び出し、defs 専用の MIR 関数群を文字列として受け取る(`func_defs_mir`)。
- その後、`internal` 配下の多数の `Lower*Box.try_lower` を順番に適用し、Program 全体を 1 関数(`main`)ベースの MIR(JSON) に落とす。
- `FuncLoweringBox` の現状:
- `lower_func_defs` は Program(JSON) から `defs` 配列をパースし、各 def ごとに `_lower_func_body` を呼ぶ。
- `_lower_func_body` がサポートするのは **単一の Return を持つ最小パターンのみ**:
- `Return(Int)`
- `Return(Binary(op, lhs, rhs))``+,-,*,/` のみ、かつ `Int/Var` 組み合わせ限定)
- `Return(Call(name, args))`Call 名は `func_map` を用いて `Box.method` に解決)
- 複数ステートメント、`If``Loop`、メソッドチェインなど Stage1 CLI に実際に現れる構造はすべて `null` でスキップされる。
- `MirBuilderBox` の挙動:
- 何らかの `Lower*Box` が Program 全体にマッチした場合は、その MIR(JSON) に対して `_norm_if_apply` を通し、`FuncLoweringBox.inject_funcs` で defs 分の MIR 関数を **追加注入** する。
- どの `Lower*Box` もマッチしないが `func_defs_mir` が非空の場合は、`"{\"functions\":[" + defs + "]}"` という最小モジュールを組み立てて `_norm_if_apply` に渡す。
- このケースでは main 関数を含まない defs だけの MIR モジュールになり、ny-llvmc 側でエントリポイント不足や空挙動 EXE を生む原因になる。
- `func_defs_mir` も空で internal lowers も不発の場合は `null` を返し、最後に provider delegate`env.mirbuilder.emit`)へフォールバックする。
- 現時点での不足点要約・2025-11-16:
- `FuncBodyBasicLowerBox` が本番でカバーできているのは、Local/If/Return の基本形LoopForm 正規化済み Loopごく一部の MethodCall`args.size/get``String.length` の Return 形に限られる。Stage1 CLI のような複雑な関数本体(複数 LocalIf ネストLoopMethod/Extern 混在)は、ほぼすべて Rust provider 経路にフォールバックしている。
- ExternCall は `hostbridge.extern_invoke("env.codegen","emit_object"|"link_object", arg)``ExternCallLowerBox` で最小サポートしているだけで、それ以外の extern`env.mirbuilder.emit` や console 系など)は現状 selfhost builder の対象外。
- `HakoCli.run` 専用 lower`CliRunLowerBox`)は MVP 用のシンプルな run パターンのみを想定しており、実際の `launcher.hako` の runusage/unknown ログ付き)は shape mismatch で selfhost 降ろしの対象外。ここを Stage1 実形に合わせて広げることが Phase 25.1b の中心タスクになっている。
### Stage1 EXE / build_stage1.sh の現状
- `tools/selfhost/build_stage1.sh`:
- 既定値 `HAKO_SELFHOST_BUILDER_FIRST=0`provider-firstでは、Stage1 EXE ビルドは成功し、`emit program-json` / `emit mir-json` / `build exe` の I/O も Rust/llvmlite と同等の JSON+exit code 契約を満たす。
- `HAKO_SELFHOST_BUILDER_FIRST=1`selfhost-firstでは、Stage1 CLI のような複雑な Program(JSON) に対して selfhost builder が「defs のみ」MIR か mini stub MIR を返し、結果として「Result: 0 だけ出す空 EXE」になる。
- スモーク:
- `tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_canary_vm.sh` は provider-first ルートのみをカバーしており、selfhost builder 単独経路のギャップは検出できていない(今後 canary を追加する必要がある)。
## 25.1b のスコープ(案)
- selfhost builder 本体Hakorune 側):
- `Program.defs` の処理を実装し、`box + method` ごとに MIR 関数を生成する。
- 例: `HakoCli.run` / `HakoCli.cmd_emit_program_json` / `HakoCli.cmd_emit_mir_json` / `HakoCli.cmd_build_exe` 等。
- `call` / `BoxCall` / `ExternCall` の解決(`func_lowering` + call resolve 相当)を Hakorune 側にも実装し、`Call("cmd_emit_mir_json")``Global` callee に解決できるようにする。
- Loop / branch / compare / Array/Map 操作など Stage1 CLI で出現する構造を包括的に lowering するため、`lang/src/mir/builder/internal/*` の helper を本番経路に統合する。
- JSON 出力:
- 現状の `"{\"functions\":[...]}\"` ベタ書きから、jsonfrag 等を用いた構造的出力に切り替え、複数関数を同一モジュールに含められるようにする。
- 既存の mini パターン用 JSON 組み立てとの互換性を維持しつつ、Stage1 CLI で必要な関数数に耐えられる形に拡張する。
- 運用ポリシー:
- Phase 25.1b 中は `HAKO_SELFHOST_BUILDER_FIRST=0` のままprovider-firstとし、selfhost builder が Stage1 CLI を lowering し切れることを確認した時点で `=1` への切り替えを検討する。
- lambda/FunctionBox (`NewClosure` 等) は本フェーズでは扱わず、従来どおり builder からは排除したままにする(別フェーズ 25.2b に委ねる)。
## Guardrails / ポリシー
- Rust Freeze Policy:
- Rust 側の Program→MIR 実装には原則手を入れず、selfhost builder は「Rust の既存挙動に合わせる」方向で実装する。
- 変更は Hakorune 側 (`lang/src/mir/builder/*`) とツール (`tools/hakorune_emit_mir.sh`) に閉じる。
- FailFast:
- selfhost builder が Program(JSON) の一部に対応していない場合は、明確なタグ付きで失敗させる(例: `[builder/selfhost-first:unsupported:Match]`ようにし、silent stub には戻さない。
- provider 経路は退避路として残しつつ、Stage1 CLI の代表ケースでは selfhost builder が先に成功することを目標にする。
### LoopForm / PHI ポリシー(重要メモ)
- ループを含む関数本体を selfhost builder で扱うときは、**LoopForm 正規化を前提にする**:
- 可能な限り `docs/guides/loopform.md` で定義された「キャリア1個の φ」モデルに従う。
- break/continue を含むループは、LoopForm の制約更新変数最大2個・セグメント整列などを満たす範囲でのみ lowering 対象にする。
- MirBuilder 側で「生の while/for を直接 MIR の PHI に落とす」ような adhoc 実装は行わない:
- PHI ノードの生成・配置は既存の LoopForm/LowerLoop 系 helper`loop_scan_box.hako``lower_loop_*_box.hako` などに一元化し、builder 本体はそれを利用する立場にとどめる。
- LLVM harness 側の PHI 不変条件ブロック先頭グルーピングwelltyped incomingを崩さない。
- Phase 25.1b では:
- まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/forから対応し、
- LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの`[builder/selfhost-first:unsupported:loopform]` タグなどで FailFast する。
#### LoopForm 複雑ケースへの拡張方針Rust builder をオラクルに使う)
- ねらい:
- 複雑な LoopFormキャリア複数・条件付き更新などについては、**Rust 側 MirBuilder/LoopForm 実装を「正解(オラクル)」として扱い**、Hakorune 側の `LowerLoop*Box` 群をそれに追従させる。
- Hakorune 側は LoopForm の設計や PHI 配線を再実装せず、「入力 JSON のパターンマッチ+既存 LowerLoop* の呼び出し」に専念する。
- 手順イメージ:
1. Rust 側 loop スモーク(例: `docs/development/roadmap/phases/phase-17-loopform-selfhost/``phase-21.6/21.8` 関連)に対応する .hako を特定し、provider-first`HAKO_SELFHOST_BUILDER_FIRST=0`)で MIR(JSON) を採取する。
2. 同じ .hako を selfhost-first`HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_MIR_BUILDER_FUNCS=1 HAKO_SELFHOST_TRACE=1`)で通し、`LowerLoop*Box` がどこまで拾えているか/どのケースが `[builder/funcs:unsupported:loopform]` になっているかを観測する。
3. 差分が出ているループだけを対象に、小さな `LowerLoopXXXBox`(または既存 LowerLoop* の強化)を追加する。
4. ループの意味論差異キャリア更新・退出条件・rcが出ていないかは、VM/EXE canaryrc チェック)で確認する。
- ガード:
- 新しい LoopForm 対応はすべて既存 `lower_loop_*_box.hako` 群の中に閉じ込め、FuncLowering/MirBuilder 本体では依然として「LoopForm 結果を `_rebind` で名前付けするだけ」にとどめる。
- Rust 側の LoopForm/PHI 実装を変えずに selfhost 側のカバー率だけを上げるのが Phase 25.1b の範囲。
#### 25.1b で追加する LoopForm 用の新箱(足場)
- `lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako`
- 目的:
- Fibonacci 風の「multi-carrier」ループ`i,a,b,t` など複数のキャリアを持つループ)を selfhost builder 側で検出し、将来的に LoopFormBox に委譲できるようにする足場。
- 現段階の挙動:
- Program(JSON v0) 内に
- `type:"Loop"``type:"Compare"` が存在し、
- ループ本体に `type:"Local"` が複数存在する(=キャリア候補が複数ある)
ことを確認したら、`HAKO_MIR_BUILDER_TRACE=1` 相当builder_config.trace_enabled有効時に
`[mirbuilder/internal/loop:multi_carrier:detected]` タグを出す。
- まだ LoopFormBox への `build2` 委譲は行わず、`null` を返して Rust provider 経路にフォールバックするFail-Fast ポリシー維持)。
- 今後の拡張:
- `LoopScanBox` を使ってキャリア変数群・limit などを抽出し、`LoopOptsBox.build2(opts)` に渡すロジックを追加する。
- 代表ケースとして `tools/smokes/v2/profiles/quick/core/vm_loop_phi_multi_carriers.sh` と同型の .hako を selfhost-first で通す canary を追加し、VM/EXE rc を Rust オラクルと比較する。
## Next Steps実装フェーズに入るときの TODO
1. 調査:
- Rust 側 `program_json_to_mir_json_with_imports` の挙動をトレースし、どの AST ノードがどの MIR に降りているかを整理(特に defs/call/loop/boxcall
- selfhost builder の現行 JSON 生成経路を洗い出し、stub を生成している箇所を特定。
2. 設計:
- `Program.defs` → MIR 関数生成のインタフェース(必要なフィールドと lowering 手順)を定義。
- call resolve 用の軽量マップ(`name -> qualified`)を selfhost builder に導入する。
3. 実装:
- defs 対応・call resolve・loop/branch lowering を段階的に導入しつつ、各ステップで mini スモークを追加。
- jsonfrag ベースの出力に切り替えながら、既存の mini テストを全て通ることを確認。
4. 検証:
- `tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako …``HAKO_SELFHOST_BUILDER_FIRST=1` で実行し、62KB クラスの MIR(JSON) が selfhost builder 単独で得られることを確認。
- `tools/selfhost/build_stage1.sh` を selfhost-first でビルドし、Stage1 CLI EXE が `emit`/`build` コマンドを正しく実行できるかJSON stdout + exit codeをスモークで検証。
## 設計 TODOFuncLoweringBox / MirBuilderBox 拡張の方向性)
※ ここから先は「具体的にどこをどう広げるか」の設計メモ。実装はまだ行わない。
1. FuncLowering の対応範囲拡張
- `_lower_func_body` を Stage1 CLI で実際に使われているパターンまで広げる:
- 単一 Return だけでなく、ローカル変数定義if 分岐loop を含む「典型的な CLI ハンドラ」の形をサポート。
- `MethodCall``args.size()` / `args.get(i)` / `FileBox.open/read/write` / `ArrayBox.push` など)を MIR `mir_call``call` に落とす処理を追加。
- `func_map` / `resolve_call_target` を用いて、`HakoCli.cmd_emit_*` / `cmd_build_exe` などの内部 Call を `Global("HakoCli.cmd_emit_*")` 系に正規化。
2. MirBuilder 本体の出力構造
- これまでの「Program 全体を 1 関数 main に落とす」前提から、「Program.body + defs を multifunction MIR モジュールに落とす」前提へシフト:
- `Program.body` からはエントリ `Main.main/1` 相当の MIR 関数を生成。
- `Program.defs` からは `HakoCli.*` などの補助関数を生成。
- `_norm_if_apply` / `inject_funcs` の役割を整理し、「main 関数を含まない defsonly モジュール」を返さないように FailFast する。
3. FailFast とデバッグ
- Stage1 CLI のような大きな Program(JSON) に対して selfhost builder が未対応の場合は:
- `[builder/selfhost-first:unsupported:func_body]` などのタグ付きで明示的に失敗。
- provider 経路(`env.mirbuilder.emit`)へのフォールバックは維持するが、「空 EXE になる stub MIR」は生成しない方針に切り替える。
- `HAKO_SELFHOST_TRACE=1` 時に、FuncLowering がどの def でどこまで lowering できたかをログに出す。
4. 検証計画
- selfhostfirst canary:
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhostfirst 版を追加し、`HAKO_SELFHOST_BUILDER_FIRST=1` `MirBuilderBox.emit_from_program_json_v0` だけで 60KB 級の MIR(JSON) を生成できることを確認。
- Stage1 build:
- `tools/selfhost/build_stage1.sh` を selfhost-first で回し、生成された Stage1 EXE に対して `emit program-json` / `emit mir-json` / `build exe` スモークを追加。
このファイルは引き続き「Phase 25.1b の計画メモ(設計ディープダイブ)」として扱い、実装は Phase 25.1a の安定化完了後に、小さな差分に分割して順次進める。***
---
## 実装計画(順番にやる TODO
備考: ここでは Phase 25.1b を「複数の最小ステップ」に分解して、順番/ゴール/ガードを具体的にメモしておくにゃ。
### Step 0 — FailFast・観測を揃える
- Status: implemented (2025-11-15). `MirBuilderBox` now tags `defs_only` / `no_match` failures and aborts, and `FuncLoweringBox` logs unsupported defs when `HAKO_SELFHOST_TRACE=1`.
- 目的: 既存の selfhost builder がどこで諦めているかを正確に観測し、stub MIR を返さずに Fail させる導線を整える。
- 作業:
- `MirBuilderBox.emit_from_program_json_v0`
- `func_defs_mir` だけが非空だった場合でも黙って `{ "functions": [defs] }` を返さず、`[builder/selfhost-first:unsupported:defs_only]` を出す。
- internal lowers がすべて `null` の場合、`[builder/selfhost-first:unsupported:<reason>]` のタグを付与。
- `FuncLoweringBox.lower_func_defs`
- どの関数名で `_lower_func_body``null` を返したかを `HAKO_SELFHOST_TRACE=1` でログ出力。
- `tools/hakorune_emit_mir.sh`
- 既存の head/tail ログ出力で `[builder/selfhost-first:*]` タグがそのまま表示されることを確認済み(追加改修なし)。
- 成果物:
- selfhost-first で Stage1 CLI を通したときに、どの関数/構造がまだ未サポートなのかがログで推測できる状態。
### Step 1 — defs injection の再設計
- Status: initial-implementedmain 必須チェックはトグル付き; multi-function への完全移行は後続 Step
- 目的: `FuncLoweringBox.inject_funcs` で main 関数の有無を意識し、multi-function モジュールの土台を整える。
- 作業:
- `inject_funcs` 内で `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` のときに `"name":"main"` を含まない `functions` 配列を拒否し、`[builder/funcs:fail:no-main]` をトレース出力して injection をスキップする(既定では OFF なので既存挙動は維持)。
- 将来フェーズで、`Program.body` から生成した main と defs を「2段構え」でマージする APImain 名の受け渡しなど)を追加する。
- 成果物(現段階):
- Env トグルを有効化した状態では「main を含まない MIR に defs だけ差し込む」ケースを検知できるようになり、Stage1 実装時に安全に stricter モードへ移行する足場ができた。
### Step 2 — `_lower_func_body` の拡張ローカルif
- 目的: Stage1 CLI の `HakoCli.cmd_emit_program_json` のような「Local / Assign / If / Return だけで構成された関数」を selfhost builder で lowering できるようにする。
- 作業:
- Body を JsonFrag で走査し、ローカル導入・代入・分岐を MIR ブロックへ展開する最小ロジックを FuncLoweringBox に追加。
- Loop が出た時は `[builder/funcs:unsupported:loop]`(仮タグ)を出して Fail-FastStep 3 で LoopForm 対応を行う)。
- 成果物:
- Loop を含まない defs は selfhost builder で MIR 関数にできるようになり、Stage1 CLI の emit/build ハンドラの半分程度を selfhost パスで賄える。
### Step 3 — LoopLoopFormの受理
- Status: initial-implemented (2025-11-15). `FuncBodyBasicLowerBox` now calls `LowerLoopSumBcBox`/`LowerLoopSimpleBox` from `_try_lower_loop`, tags unsupported loops with `[builder/funcs:unsupported:loopform]`, and delegates all PHI/carrier handling to LoopForm lowers.
- 目的: LoopForm 正規化済みの while/for を MIR に落とす。ループの正規化・PHI設計はLoopForm/既存lower_loop系Boxに任せ、FuncLowering/MirBuilder側はそれを使うだけにする。
- 作業内容(実装済み):
- `FuncBodyBasicLowerBox``_try_lower_loop`メソッド追加:
- Loop判定 → `LowerLoopSumBcBox.try_lower``LowerLoopSimpleBox.try_lower` の順に試す。
- 成功時は`_rebind`で関数名を`Box.method/arity`に付け替え。
- 失敗時は`[builder/funcs:unsupported:loopform]`でFail-Fast。
- `lower`メソッド冒頭でLoop優先処理:
- Loop含む場合は`_try_lower_loop`を呼び、成功/失敗で明確に分岐。
- Loopが無い場合のみ既存のLocal/If/Return処理に進む。
- PHI地獄防止ポリシー徹底:
- FuncBodyBasicLowerBox/FuncLowering側でPHIやキャリアを直接いじらない。
- LoopForm制約外は必ずタグ付きでFail-FastRust providerに退避可能
- 成果物:
- `cmd_build_exe``loop(i < argc)`等、Stage1 CLIの代表的なwhile/forパターンをselfhost builderで通せる基礎が整った。
- 次のステップ: LoopForm対応の動作確認スモークテスト追加、Step4MethodCall/ExternCallへ進む。
### Step 4 — MethodCall / ExternCall パリティ設計メモ・Rust層読解込み
- Status: design-onlyRust 層の挙動を踏まえた設計まで)
- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現し、Rust 側の `build_method_call` / extern handler と意味論を揃える(ただしスコープは Stage1 必要最小限に限定)。
- 対象Phase 25.1b で扱う範囲に限定):
- Stage1 CLI (`lang/src/runner/launcher.hako`) 内で出現する代表パターン:
- `FileBox` 系: `fb.open(path,"r")` / `fb.read()` / `fb.write(content)` / `fb.close()`
- `ArrayBox` 系: `args.size()` / `args.get(i)` / `args.push(v)`
- `MapBox` 系(必要になれば): `m.get(k)` / `m.set(k,v)` / `m.size()`
- `String` 系: `s.length()`(現状 Step2 でも使われている)
- self-call: `me.cmd_emit_program_json(args)` / `me.cmd_emit_mir_json(args)` / `me.cmd_build_exe(args)` など
- ExternCall 的なもの: `hostbridge.extern_invoke("env.codegen","emit_object",args)` / `hostbridge.extern_invoke("env.codegen","link_object",args)`
- 設計方針:
1. **MethodCall → mir_call(Method)**box メソッド呼び出し)
- StageB Program(JSON v0) での形(実測):
- `return arr.size()` は defs 内で次のように現れる:
```json
{"type":"Local","name":"arr","expr":{"type":"New","class":"ArrayBox","args":[]}}
{"type":"Return","expr":{
"type":"Method",
"recv":{"type":"Var","name":"arr"},
"method":"size",
"args":[]
}}
```
- `hostbridge.extern_invoke("env.codegen","emit_object", a)` は:
```json
{"type":"Expr","expr":{
"type":"Method",
"recv":{"type":"Var","name":"hostbridge"},
"method":"extern_invoke",
"args":[
{"type":"Str","value":"env.codegen"},
{"type":"Str","value":"emit_object"},
{"type":"Var","name":"a"}
]
}}
```
- FuncLoweringBox / FuncBodyBasicLowerBox 側で扱う基本パターン:
- `Return(Method recv.method(args))` を検出し、
- `recv` が **パラメータ由来の Var**(例: `args.size()`)のときだけ selfhost lowering 対象にする。
- ローカル由来(`local fb = new FileBox(); return fb.read()`)は Phase 25.1b では対象外とし、今後のフェーズでローカルテーブル導入後に扱う。
- 引数は Int/Var/String のみ対象lambda や複合式は未対応)。
- MIR 側では `mir_call` の Method 形を使う:
```json
{"op":"mir_call","dst":R,
"mir_call":{
"callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":recv_reg},
"args":[ /* 必要に応じて追加 */ ],
"effects":[]
}}
```
- `box_name` / `method` は whitelisted な Box のみ(当面は `ArrayBox` と `String` 程度)をハードコードで対応。
- receiver は `receiver` フィールド(または args の先頭でレジスタ番号を渡し、Rust 側の `mir_call` 仕様と揃える。
2. **self-callme.cmd_*)の解決**
- Stage1 CLI の `me.cmd_*` は、StageB の FuncScanner から `Program.defs` に `box:"HakoCli", name:"cmd_*"` として載る。
- これを FuncLoweringBox の `func_map` に登録済みなので、
- `Call("cmd_emit_mir_json", args)` → `func_map("cmd_emit_mir_json") = "HakoCli.cmd_emit_mir_json"`
- という形で Global 関数名を解決できる。
- Step4 では `Return(Call("cmd_*", ...))` だけでなく、「単独の Call 文」や「MethodCall 内からの self-call」も対応させる余地があるが、
- Phase 25.1b ではまず `Return(Call(...))` パターンの範囲内で self-call を `Box.method/N` に揃えるところまでに留める(広げるのは後続フェーズ)。
3. **ExternCallhostbridge.extern_invokeの扱い**
- Rust 側では `hostbridge.extern_invoke("env.codegen","emit_object",args)` 等を特別扱いし、C-ABI 経由で `env.codegen` provider にルーティングしている。
- selfhost builder 側では、Stage1 CLI の以下のパターンのみをサポート対象とする:
- `"env.codegen","emit_object",[mir_json]`
- `"env.codegen","link_object",[obj_path,(out_path)]`
- `"env.mirbuilder","emit",[program_json]`(必要なら)
- JSON 上では `expr.type:"Method"` `recv:Var("hostbridge")` `method:"extern_invoke"` で表現されているので、
- `args[0]` / `args[1]` が `"env.codegen"`, `"emit_object"` or `"link_object"` であることを確認し、
- static なパターンマッチで MIR の `extern_call` に落とす:
```json
{"op":"externcall","func":"env.codegen.emit_object","args":[ /* regs */ ]}
```
- ここでは「すべての extern を一般化する」のではなく、Stage1 CLI が実際に使っている env 名とメソッド名だけを point fix するRust Freeze Policy に従い、意味論は Rust 版を真似るが範囲は狭く保つ)。
4. **未対応パターンの Fail-Fast**
- MethodCall/ExternCall の lowering 中に、
- 複雑なオブジェクト式(ネストした MethodCall/Array/Map リテラルなど)、
- 引数に対応していない型lambda など)、
- 未サポートの env 名 / メソッド名(`env.codegen` 以外)、
が見つかった場合は、`[builder/funcs:unsupported:call]` タグを出して `null` で戻る。
- これにより、「知らない形をなんとなく MIR にする」ことを避け、Rust provider や legacy CLI delegate に退避できるようにする。
- 実装イメージPhase 25.1b 中にやるときの TODO:
1. `FuncLoweringBox` に小さな helper を追加:
- `_lower_method_call(body_json, func_name, box_name, params_arr)` → MethodCall パターン検出+`mir_call Method` 生成。
- `_lower_hostbridge_extern(body_json, ...)` → env.codegen/env.mirbuilder 用の最小 ExternCall 生成。
2. `_lower_func_body` の冒頭か、既存 `Return(Call)` の前後でこれら helper を呼び出し、マッチした場合のみ MIR を返す。
3. Tag/ログ:
- `HAKO_SELFHOST_TRACE=1` 時に `[funcs/basic:method.*]` / `[funcs/basic:extern.*]` trace を出し、どの defs が Method/Extern 経由で lowering されたか観測できるようにする。
4. スモーク:
- `tools/smokes/v2/profiles/quick/core/phase251` に `selfhost_mir_methodcall_basic_vm.sh` のような canary を追加し、
- ArrayBox.push / FileBox.open/read/write / env.codegen.emit_object/link_object の代表ケースを selfhost builder-first で通し、
- `mir_call` / extern call が出力 MIR に含まれていることを確認する。
### Step 1 — CLI entry detection (CliEntryLowerBox)
- 目的: Stage1 CLI の入口構造(`Main.main` → `HakoCli.run`)を Program(JSON v0) 上で検出し、selfhost builder が「どの Program が CLI エントリを含むか」を把握できるようにする(観測専用)。
- 作業:
- `lang/src/mir/builder/func_body/cli_entry_box.hako` に `CliEntryLowerBox` を追加。
- `scan(program_json)` で以下を確認し、すべて満たすときだけタグを出す:
- `Program.body` 内に `New(HakoCli)` 相当の JSON`"class":"HakoCli"`)が存在する。
- `defs` 配列に `{"box":"HakoCli","name":"run", ...}` が存在する。
- 入口付近に `method":"run"` を含む Method 呼び出しがある(軽いヒューリスティック)。
- 条件を満たした場合、`HAKO_SELFHOST_TRACE=1` のときに
`[builder/cli:entry_detected] main=Main.main run=HakoCli.run/2`
を出力し、戻り値は常に `null`MIR は生成しない)。
- `FuncLoweringBox.lower_func_defs` の先頭で `CliEntryLowerBox.scan(program_json)` を呼び出し、defs 降ろし処理とは独立に「入口構造だけを観測」できるようにする。
- レイヤリング:
- この Step1 はあくまで ring1Stage1 CLI ソース)の Program(JSON v0) を観測するだけで、ring0Rust env.mirbuilder/env.codegenには一切影響を与えない。
- 今後の Step2/3 で `HakoCli.run` / `Main.main` の本体を MIR に降ろすときの「入り口インデックス」として使う想定。
### Step 2 — HakoCli.run 形状スキャンCliRunShapeScannerBox lower stub
- 目的: Stage1 CLI の `HakoCli.run` がどのような分岐run/build/emit/check 等)を持っているかを Program(JSON v0) から把握し、将来の専用 lowerCliRunLowerBoxが安全に使えるかを事前に観測する。
- 作業:
- `lang/src/mir/builder/func_body/cli_run_shape_box.hako` を追加CliRunShapeScannerBox
- `scan(program_json)` で:
- `{"box":"HakoCli","name":"run", ...}` を defs 内から検出し、
- Program 全体の文字列から `"run"`, `"build"`, `"emit"`, `"check"` などのリテラルを簡易に拾って、`branches` 配列として記録。
- `HAKO_SELFHOST_TRACE=1` のときに
`[builder/cli:run_shape] has_run=1 branches=<count>`
を出力し、戻り値として `{"has_run":1,"branches":[...]} (MapBox)` を返す(現状はタグ主体で利用)。
- `lang/src/mir/builder/func_body/cli_run_lower_box.hako` を追加CliRunLowerBox
- 現段階では stub 実装:
- `lower_run(func_name, box_name, params_arr, body_json, func_map)` は `HakoCli.run` だけをターゲットにし、`HAKO_SELFHOST_TRACE=1` 時に
`[builder/cli:run_lower:stub] box=HakoCli func=run`
を出して常に `null` を返す。
- 実際の MIR 降ろしrun/build/emit/check 分岐を持つ run 本体の loweringは、Step2 後半〜Step3 以降で実装する前提。
- `FuncLoweringBox` への統合:
- `lower_func_defs` の先頭で `CliRunShapeScannerBox.scan(program_json)` を呼び、Stage1 CLI run の形状をタグ付きで観測。
- `_lower_func_body` の冒頭で `box_name=="HakoCli" && func_name=="run"` のときだけ `CliRunLowerBox.lower_run(...)` を呼び出す。現状は常に `null` なので、従来の BasicLowerBox / provider 経路と挙動は変わらない。
- レイヤリング:
- Step2 も Step1 と同様、ring1Stage1 Hako CLIの構造を観測する箱のみを追加する。
- MIR 生成はまだ Rust provider /既存 lower に任せており、ring0 の責務env.* extern や実行エンジン)にも影響を与えない。
- 専用 lower`CliRunLowerBox` が実際に MIR を返す形は、StageB Program(JSON v0) の形状を十分観察してから、小さなパターン(シンプルな run/build/emit/check 分岐)ごとに段階的に実装する。
### Step 2.x — HakoCli.run lowering設計メモMVP 実装状況)
- ゴール:
- `HakoCli.run(me,args)` のうち「単純な run/build/emit/check 分岐」だけを selfhost builder で MIR 関数に降ろす。
- 形が少しでも崩れていたら必ず `null` を返し、Rust provider にフォールバックするFailFast
- 対象とする JSON v0 の形MVP 想定):
1. `Local argc = Int(0)`
2. `If cond Var("args") then { Local argc = Method Var("args").size() }`
3. `If cond Compare(Var("argc") == Int(0)) then { Return Int(1) }`
4. `Local cmd_raw = Method Var("args").get(Int(0))`
5. `Local cmd = Binary("+", Str(""), Var("cmd_raw"))`
6. 連続する `If` で `cmd == "run"|"build"|"emit"|"check"` を判定し、それぞれ `Return Call("cmd_*", [me,args])` を持つ。
7. 最後に `Return Int(2)`unknown commandを持つ。
- 実装状況CliRunLowerBox 内):
1. ターゲット判定(実装済み)
- `box_name=="HakoCli" && func_name=="run"` 以外は即 `null`。
2. 構造パターンの検証 `_check_shape`(実装済み)
- `body_json` を文字列として走査し、上記 1〜7 のステートメントが順番どおりに現れているかを `JsonFragBox` で確認(ローカル名やメソッド名も一致させる)。
- OK のとき `1`、不一致のとき `0` を返し、`HAKO_SELFHOST_TRACE=1` で `[builder/cli:run_lower:shape-ok]` / `[builder/cli:run_lower:unsupported]` を出す。
3. MIR 生成MVP `_emit_mir`(実装済み・既定 OFF
- params_arr=["me","args"] を r1,r2 とみなし、固定レイアウトのレジスタ配置で簡略 MIR(JSON) を構築。
- ブロック構造(要約):
- argc を `boxcall size(box=2)` で計算し、`argc==0` のときは `ret 1`。
- `args.get(0)` で cmd_raw を取得し、`"run"|"build"|"emit"|"check"` との比較で `cmd_run/cmd_build/cmd_emit/cmd_check` を `boxcall` で呼び出してそのまま ret。
- どれにもマッチしない場合は `ret 2`unknown command
- 環境変数 `HAKO_MIR_BUILDER_CLI_RUN=1` のときにだけ `_emit_mir` を呼び、それ以外は shape OK でも `null` を返して provider/既存 lower にフォールバックする(既定挙動は不変)。
4. タグと FailFast実装済み
- 形が完全に一致し、`HAKO_MIR_BUILDER_CLI_RUN=1` のときにだけ MIR を返し、`HAKO_SELFHOST_TRACE=1` で `[builder/cli:run_lower:ok] HakoCli.run/2` を出す。
- 途中でパターンが崩れていた場合は `[builder/cli:run_lower:unsupported] reason=...` を出して `null` を返すprovider が引き継ぎ)。
5. 現在のカバレッジ:
- `.hako` に HakoCli.run + cmd_* を直接書いた最小ケースでは、selfhost builder だけで `HakoCli.run/2` の MIR(JSON) を生成できることを
`tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` で確認済み。
- 実際の Stage1 launcher.hako の `run` は usage メッセージ出力などを含むため、この MVP はまだ Stage1 本体には適用していない(今後 StageB Program(JSON v0) を詳細に比較しながら対応範囲を広げる)。
#### Stage1 HakoCli.run本番とのギャップ整理provider MIR ベース)
現状、このホスト環境では StageB 実行中に `Undefined variable: local`Stage3 キーワード)で Program(JSON v0) 抽出が失敗しているため、実 `launcher.hako` の HakoCli.run 形状は Rust provider の MIR(JSON) から推定している(`/tmp/launcher_provider_mir.json` で確認)。
MVP run パターンと Stage1 実装の主な差分:
- argc==0 の場合:
- MVP: `if argc == 0 { return 1 }`(副作用なし)。
- 実装: usage メッセージ `[hakorune] usage: hakorune <command> [options]` を `print` してから `ret 1`。
- サブコマンド unknown の場合:
- MVP: 単純に `return 2`。
- 実装: `[hakorune] unknown command: <cmd>` を `print` してから `ret 2`。
- 比較まわり:
- 両者とも `"run"|"build"|"emit"|"check"` 文字列と一致判定しているが、実装側は usage/unknown メッセージ用の追加 `binop`/`call` を複数ブロックに分割しているMIR 上でブロック数が多い)。
- 呼び出しターゲット:
- 両者とも `cmd_run/cmd_build/cmd_emit/cmd_check` を呼び出す点は一致MIR 上では `boxcall`、将来 `FuncLowering` の call_resolve で Global/Method に寄せる予定)。
今後 StageB Program(JSON v0) が安定して取れるようになったら、上記の差分を JSON レベルで再確認し、
- usage/unknown の `print` ブロックを「前置/後置のサイドエフェクト」として `_check_shape` の許容パターンに追加するか、
- あるいは run 本体を「MVP サブセット(引数分岐)+印字専用ブロック」に分けて扱うか、
を決める予定。
#### Stage1 用 run パターン拡張方針(設計)
Stage1 launcher.hako の本番 `HakoCli.run` を selfhost lowering の対象に含めるための方針は次のとおり:
- 目的:
- Rust CLIStage0と同じ意味論exit code とメッセージを維持したまま、Stage1 側の run を selfhost builder からも扱えるようにする。
- logging/usage 部分printは「サイドエフェクトのある前置/後置ブロック」として明示的に扱い、分岐ロジック本体とは分離して考える。
- 拡張の方向性(案):
1. **前置 usage ブロックの許容**
- `_check_shape` で「argc==0 → usage print → ret 1」という形を、
「`argc == 0` の then 側に `StringBox` const / `binop` / `call(print)` が含まれてもよい」
というルールに緩和する。
- lowering 時には:
- まず usage 用の文字列構築+`print` をそのまま MIR に反映(`boxcall` / `externcall env.console.log` など)。
- そのあとで `ret 1` を emit するMVP では usage 文言は provider MIR に揃える)。
2. **unknown ブロックの後置許容**
- MVP では「unknown なら `ret 2`」のみを扱っているが、本番では
`[hakorune] unknown command: <cmd>` を出力してから `ret 2` している。
- `_check_shape` を「末尾の Return Int(2) の前に StringBox/`binop`/`print` パターンが挟まっていてもよい」と解釈できるようにし、
lowering 側でもそれをそのまま MIR に降ろすprint ブロック + `ret 2`)。
3. **run 本体(分岐ロジック)との分離**
- `_check_shape` を二層に分ける:
- `check_core_shape` … argc/args/cmd/サブコマンド分岐の「副作用なし」部分の形状チェック。
- `check_logging_shape` … usage/unknown の印字パターンのみを許容する緩いチェック。
- `CliRunLowerBox` はまず core を `_emit_mir_core` で生成し、logging 部分は必要に応じて前後にブロックを足す形で統合する。
- トグルと適用範囲:
- Stage1 本番への適用は常に `HAKO_MIR_BUILDER_CLI_RUN=1`(既定 OFFでガードし、
かつ StageB Program(JSON v0) で `check_core_shape` / `check_logging_shape` の両方を満たしている場合だけ有効にする。
- それ以外のケースprint の形がずれている / 追加の case が増えたなど)は、今まで通り provider 経路に退避する。
### Step 5 — call resolve / methodize 後処理
- 目的: 生成済み MIR に対して call resolver / methodize pass をかけ、Rust provider と同じ命名・呼び出し形式を実現。
- 作業:
- `HAKO_MIR_BUILDER_CALL_RESOLVE=1` を本格利用し、call の `Const("Box.method/N")` を `mir_call` に変換。Stage1 CLI で `mir_call Method` を使うケースをテストし、Methodize との組み合わせでも崩れないことを確認。
### Step 6 — selfhost-first canary / build_stage1.sh
- 目的: selfhost builder を既定 ON に戻す準備。
- 作業:
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhost-first 版を追加して selfhost builder 単独で 60KB 級 MIR を生成できることを検証。
- `tools/selfhost/build_stage1.sh` を selfhost-first で回し、selfhost builder 由来の MIR で `emit program-json` / `emit mir-json` / `build exe` が通ることを確認。
- 問題が無ければ `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻す別PRでも可
作業順は Step 0 → 1 → 2 → 3 → 4 → 5 → 6 を想定、各ステップで必ず docsこのファイルCURRENT_TASKとスモーク/テストを更新する。
現状2025-11-16 時点)の進捗:
- Step0〜3: 実装済みFail-Fast 導線・Local/If/Return 基本形・LoopForm 正規化済み Loop の取り込み)。
- Step4:
- MethodCall: `FuncBodyBasicLowerBox._try_lower_return_method` で `Return(Method recv.method(args))` 形のうち、ArrayBox.size/getparams ベース receiverと StringBox.length引数なしを最小カバー済み。生成される MIR は `mir_call { callee: { type: "Method", box_name: "<ArrayBox|StringBox>", method, receiver }, args: [...] }` 形式。
- ExternCall: `lang/src/mir/builder/func_body/extern_call_box.hako` に `ExternCallLowerBox.lower_hostbridge` を追加し、`Return(hostbridge.extern_invoke("env.codegen","emit_object"|"link_object", Var(arg)))` を `externcall env.codegen.emit_object|link_object``ret` に lowering する最小パターンを実装。`FuncLoweringBox._lower_func_body` から BasicLowerBox の次に呼び出すよう配線。
- Canary: `tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_vm.sh` / `selfhost_mir_extern_codegen_basic_vm.sh` を追加(現状は「対象パターンにまだ到達していない」場合に SKIP する canary として動作)。
- Step5〜6: 未着手Method/Extern のカバー範囲が実際の Stage1 CLI パターンまで広がった段階で selfhost-first canary / build_stage1.sh へ進める)。
### Stage1 CLI defs vs selfhost builder 対応状況(スナップショット)
Stage1 CLI ランチャ(`lang/src/runner/launcher.hako`)について、`tools/hakorune_emit_mir.sh` を provider-first`HAKO_SELFHOST_BUILDER_FIRST=0`で実行し、Rust provider が出力した MIR(JSON) から各関数の Method/Extern 風パターンを集計した結果:
- 集計コマンド(例):
- `HAKO_SELFHOST_BUILDER_FIRST=0 NYASH_JSON_ONLY=1 bash tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako /tmp/launcher_provider_mir.json`
- Python で `functions[].blocks[].instructions[].op in {"boxcall","mir_call","externcall"}` を走査し、Method/Extern らしき箇所を抽出。
- 注意:
- 現行の provider MIR では、Stage1 CLI のメソッド呼び出しはすべて `boxcall` で表現されており、`mir_call(Method)` にはまだ正規化されていない。
- selfhost builder は StageB の Program(JSON v0) 上で `Method` ノードを見て lowering する設計であるため、下表の「Method 名」は Program 側のメソッドセットを推定するための参考情報として扱う。
| MIR function | Method パターンprovider MIR 上の boxcall/mir_call | Extern 風パターン | selfhost builder 側の現状 |
|----------------------------------|------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|--------------------------------------|
| `HakoCli._read_file/3` | `open`, `read`, `close`FileBox 由来と推定) | なし | FileBox 系メソッドは未対応 |
| `HakoCli._write_file/5` | `open`, `write`, `close`FileBox | なし | 同上(未対応) |
| `HakoCli.cmd_build/2` | `cmd_build_exe`, `get`, `size``args.get/size` など) | なし | `args.size/get` 形は Step4 helper あり(ただし関数本体全体は未対応) |
| `HakoCli.cmd_build_exe/2` | `_read_file`, `emit_from_program_json_v0`, `emit_program_json_v0`, `extern_invoke`, `get`, `indexOf`, `push`, `size` | `extern_invoke` が hostbridge 経由の Extern 相当 | `extern_invoke("env.codegen",…)` 用 ExternCallLowerBox 追加済み/他メソッドは未対応 |
| `HakoCli.cmd_emit/2` | `cmd_emit_mir_json`, `cmd_emit_program_json`, `get`, `size` | なし | `args.size/get` のみ helper 対象候補 |
| `HakoCli.cmd_emit_mir_json/2` | `_read_file`, `_write_file`, `emit_from_program_json_v0`, `emit_program_json_v0`, `get`, `indexOf`, `set`, `size` | なし | FileBox 系/`indexOf``set` は未対応 |
| `HakoCli.cmd_emit_program_json/2`| `_read_file`, `_write_file`, `emit_program_json_v0`, `get`, `indexOf`, `size` | なし | 同上 |
| `HakoCli.run/2` | `cmd_build`, `cmd_check`, `cmd_emit`, `cmd_run`, `get`, `size` | なし | `args.size/get` helper 対象/`me.cmd_*` self-call は未対応 |
| `main` | `run``Main.main → HakoCli.run` 呼び出し) | なし | main 相当は provider/main 降ろしに依存 |
このスナップショットから分かる不足点2025-11-16 現在):
- MethodCall 側:
- Stage1 CLI で実際に使われているメソッドは、FileBoxopen/read/write/close、Array/Map 系size/get/set/push、String 系indexOfなど多岐にわたるが、selfhost builder が defs 側で専用に扱えているのは **`args.size/get` と `String.length` の単純な Return 形のみ**。
- `me.cmd_*` 系 self-call`cmd_build_exe` など)は、現状 `_lower_return_call` ベースの簡易 Call 降ろしに頼っており、Stage1 CLI の複雑な本体にはまだ対応できていない。
- ExternCall 側:
- Stage1 CLI の AOT 経路で重要な `hostbridge.extern_invoke("env.codegen","emit_object|link_object", args)` は、ExternCallLowerBox で最小対応済みだが、実際の Stage1 CLI defs からこの helper に到達しているかどうかは、今後 Program(JSON v0) 側のパターンを精査する必要がある。
- それ以外の Extern`env.mirbuilder.emit`, `env.console.*` などは、selfhost builder では現時点で扱っておらず、Rust provider / ハーネス側に依存している。
この表をベースに、今後の小さな helper 拡張(例: FileBox 用メソッド降ろし箱、Array/Map の `push/set/indexOf` 降ろし箱、`me.cmd_*` self-call 用の専用 CallLowerBox など)を段階的に追加していく予定。
Phase 25.1b の残タスクとしては、「Stage1 CLI で本当に必要なメソッドExtern パターン」だけを優先し、それ以外は引き続き Rust provider を退避路として使う方針を維持する。
### スモーク構成方針Rust builder と selfhost builder のペアリング)
- 目的:
- Rust 側の loop/method/extern スモークを **そのまま「正解」として使い回しつつ**、同じ .hako を selfhost builder で通す canary を横に並べ、「どの経路が壊れているか」をフォルダ名だけで判別できるようにする。
- 基本ルール:
- 既存の v2 スモーク構造(`tools/smokes/v2/profiles/quick/core/phaseXXXX/`は維持し、その直下に「provider-first」と「selfhost-first」の **ペアスクリプト** を置く。
- 命名例:
- `*_provider_vm.sh` … 既存どおり Rust builderprovider-first経路を確認するスモーク。
- `*_selfhost_vm.sh` … 同じ .hako / 期待 rc を selfhost-first`HAKO_SELFHOST_BUILDER_FIRST=1`)で確認するスモーク。
- ループ系:
- 例として `phase2100` の LoopForm/PHI canaryRust ベース)に対応して、
- `tools/smokes/v2/profiles/quick/core/phase2100/loop_jsonfrag_provider_vm.sh`
- `tools/smokes/v2/profiles/quick/core/phase251/loop_jsonfrag_selfhost_vm.sh`
のような組み合わせを想定(実際のファイル名は今後の実装で確定)。
- Stage1 CLI 系:
- 既存の `stage1_launcher_program_to_mir_canary_vm.sh`provider-firstに対して、
- `stage1_launcher_program_to_mir_selfhost_vm.sh`selfhost-first; builder MIR で 60KB 級出力を期待)
を `phase251` 側に追加する。
- 運用:
- quick プロファイルでは provider-first スモークを既定 ON とし、selfhost-first スモークは Phase 25.1b 中は任意(開発用)とする。
- selfhost-first スモークが十分に安定し、Stage1 build も selfhost-first で通るようになった時点で、必要に応じて CI quick プロファイルへの昇格を検討する。
***