Files
hakorune/docs/development/roadmap/phases/phase-25.1b/README.md
nyash-codex 7ca7f646de Phase 25.1b: Step2完了(FuncBodyBasicLowerBox導入)
Step2実装内容:
- FuncBodyBasicLowerBox導入(defs専用下請けモジュール)
- _try_lower_local_if_return実装(Local+単純if)
- _inline_local_ints実装(軽い正規化)
- minimal lowers統合(Return/BinOp/IfCompare/MethodArray系)

Fail-Fast体制確立:
- MirBuilderBox: defs_onlyでも必ずタグ出力
- [builder/selfhost-first:unsupported:defs_only]
- [builder/selfhost-first:unsupported:no_match]

Phase構造整備:
- Phase 25.1b README新設(Step0-3計画)
- Phase 25.2b README新設(次期計画)
- UsingResolverBox追加(using system対応準備)

スモークテスト:
- stage1_launcher_program_to_mir_canary_vm.sh追加

Next: Step3 LoopForm対応

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 22:32:13 +09:00

205 lines
18 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: planning-only実装着手前の設計・分析フェーズ
## ゴール
- 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 を使用する。
### 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`)へフォールバックする。
### 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 する。
## 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の受理
- 目的: LoopForm 正規化済みの while/for を MIR に落とす。
- 作業:
- `lower_loop_simple_box` / `lower_loop_sum_bc_box` など既存 helper を FuncLoweringBox 側からも利用できるようにし、LoopForm のキャリアを MIR へ展開。
- LoopForm の制約を満たさないケースは `[builder/funcs:unsupported:loopform]` で Fail-Fast。
- 成果物:
- `cmd_build_exe``loop(i < argc)` 等、Stage1 CLI の代表的な while/for パターンを selfhost builder で通せる。
### Step 4 — MethodCall / ExternCall パリティ
- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現。
- 作業:
- `FuncLoweringBox` に MethodCall/ExternCall の lowering ルートを追加し、`mir_call Method` もしくは `extern_call` を生成。
- `func_map` により `me.cmd_build_exe` のような self-call も Box.method/N に resolve未対応の呼び出しは `[builder/funcs:unsupported:call]` で Fail
- 成果物:
- Stage1 CLI の主要メソッド体が selfhost builder で MIR 関数化できる。
### 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とスモーク/テストを更新する。***