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>
This commit is contained in:
nyash-codex
2025-11-15 22:32:13 +09:00
parent 6856922374
commit 7ca7f646de
31 changed files with 1670 additions and 323 deletions

View File

@ -8,12 +8,18 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- `.hako → Program(JSON v0) → MIR(JSON) → EXE` のうち、**Program(JSON v0) → MIR(JSON)** の導線を再構成し、`tools/hakorune_emit_mir.sh` / `tools/selfhost_exe_stageb.sh` / `tools/selfhost/build_stage1.sh` が代表ケースで成功するようにする。
- selfhost builder / provider / legacy CLI の 3 経路が混在した現状を見直し、**信頼できる1本の Program→MIR 経路**を中心に据える。
## 進捗状況2025-11-16 時点)
- Stage-B と provider 経路で `.hako → Program(JSON) → MIR(JSON)` は安定。`hakorune_emit_mir.sh lang/src/runner/launcher.hako …``[OK] MIR JSON written (delegate:provider)`
- selfhost builder (`HAKO_SELFHOST_BUILDER_FIRST=1`) では、Stage1 CLI の Program(JSON) を完全に lowering できず 171 bytes の stub MIR を返すため、現状 selfhost-first を既定ONにできない。
- Stage1 EXE`/tmp/hakorune-dev`)は provider 経由の MIR を使えばリンク成功。ただし CLI I/O は JSON を出さず `Result: 0` のみ。JSON 出力契約に揃える作業は selfhost builder の修復後に実施予定。
## 現状の問題点2025-11-15 時点)
- `tools/selfhost/build_stage1.sh`:
- 現在の entry: `lang/src/runner/launcher.hako`Stage1 CLI ランチャー)。
- 内部で `tools/selfhost_exe_stageb.sh``tools/hakorune_emit_mir.sh` を呼び出しているが、MIR 生成フェーズで失敗
- ログ: `[FAIL] Program→MIR delegate failed (provider+legacy)`
- provider 経由 (`env.mirbuilder.emit`) では `.hako → Program(JSON) → MIR(JSON)` を安定して通せるが、selfhost builder (`MirBuilderBox` on VM) はまだ Stage1 CLI の Program(JSON) をフルで扱えず、最小パターンの stub MIR171 bytesを出力してしまう
- このため selfhost builder を既定ONにすると EXE が空挙動になるため、Phase 25.1a では暫定的に provider-first (`HAKO_SELFHOST_BUILDER_FIRST=0`) を維持しつつ、selfhost builder 側の機能範囲拡張を翌フェーズへ送っている
- `tools/hakorune_emit_mir.sh` — Program→MIR 部分:
1. StageB`compiler_stageb.hako`:
@ -21,7 +27,8 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
2. selfhost builder 経路(`try_selfhost_builder`:
- `builder_box=hako.mir.builder` で Runner を生成し、VM 経由で実行。
- 当初は tmp ハコファイルに対して `Parse error: Unexpected token FN``Unexpected token ASSIGN` が発生し rc=1 で失敗していたが、`lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()``local func_len = ...` にリネームすることで `Unexpected token FN` 自体は解消済み。
- 現在は text-merge 後の巨大一時ファイルに対して `Parse error: Invalid expression at line <N>` が出ており、prelude 連結のどこかで Stage3 構文と合わない断片が残っている状態selfhost builder は引き続き要調査)
- その後、`lang/src/shared/mir/loop_form_box.hako``lang/src/mir/builder/internal/lower_loop_count_param_box.hako` における `init` 予約語衝突(`local init = ...` / 引数名 `init`)を `start_value` 系にリネームし、LoopFormBox まわりの `Invalid expression` も解消済み
- 現在は selfhost builder Runner の実行フェーズで `VM error: Invalid instruction: global function: Unknown: self._is_on/1` が発生しており、MirBuilder 内部のトグルヘルパー `BuilderConfigBox._is_on` の呼び出し(`norm_if` ラムダ経由)が VM 側にまだ実装されていないために落ちている状態(構文ではなく実行時エラー)。
3. provider 経路(`try_provider_emit``env.mirbuilder.emit`:
- 当初は `env.mirbuilder.emit` 実行時に `[mirbuilder/parse/error] undefined variable: args` により失敗していたが、Rust 側の Program→MIR ルート修正によりこのエラーは解消済み。現在は provider 経路経由で `launcher.hako` から MIR(JSON) を安定して生成できている。
4. legacy CLI 経路(`--program-json-to-mir`:
@ -32,6 +39,16 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- `using` の解決(`lang.compiler.build.build_box`)は nyash.toml に追加済みだが、
- まだパーサが Stage3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。
## Update (2025-11-16 — StageB using resolverを module alias 化)
- `lang/src/compiler/entry/compiler_stageb.hako` で使用していたファイルパス形式の `using "..." as ...` を、`nyash.toml` `[modules]` に登録済みの module alias`hako.compiler.entry.bundle_resolver` / `lang.compiler.entry.using_resolver`)へ置き換えた。
- `tools/hakorune_emit_mir.sh` が export する `HAKO_STAGEB_MODULES_LIST` / `HAKO_STAGEB_APPLY_USINGS` を StageB が参照できるようになり、Stage3 パーサが `using` 行で失敗しなくなった(`HAKO_SELFHOST_TRACE=1 ./tools/hakorune_emit_mir.sh basic_test.hako /tmp/out.json``[emit:trace] Stage-B: SUCCESS ...` で止まらず `delegate:provider` まで進むことを確認)。
- これにより Phase 25.1a の Program→MIR ホットフィックスでは「StageB→provider delegate」が安定経路になり、selfhost builder の修復に専念できる状態になったStage1 CLI 側の `using` も module alias 化を継続)。
- 追加で、`tools/hakorune_emit_mir.sh` 側の `HAKO_MIRBUILDER_IMPORTS` 生成ロジックを拡張し、`using ns.path.Type`alias なし)の形式からも末尾セグメント(例: `MirBuilderBox`)を alias として抽出するようにした。これにより、`using lang.mir.builder.MirBuilderBox` を含む Stage1 CLI/Builder コードでも `env.mirbuilder.emit``undefined variable: MirBuilderBox` を出さずに Program(JSON v0) を lowering できるようになった。
- Stage1 CLI`lang/src/runner/launcher.hako`)の emit/build コマンドを helper (`_read_file` / `_write_file`) で整理し、Stage3 friendly な `local` 宣言・ログメッセージに統一。`hakorune_emit_mir.sh lang/src/runner/launcher.hako …` を provider delegate で再度通し、62KB 超の MIR(JSON) が得られることを quick smoke`phase251/stage1_launcher_program_to_mir_canary_vm.sh`)でカバー済み。
- Rust 側の JSON v0 ブリッジ(`src/runner/json_v0_bridge/lowering/expr.rs`)には `hostbridge` を well-known グローバルとして扱う最小の分岐を追加し、`hostbridge.extern_invoke(...)` を含む Program(JSON v0) でも `undefined variable: hostbridge` エラーで止まらないようにした(値は `Const(String("hostbridge"))` を発行する placeholder とし、実際の extern dispatch は VM/ランタイム側に委譲する)。
## フェーズ内タスク25.1a TODO
### A. Stage1 CLI ソースの VM 実行復旧
@ -48,7 +65,12 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- [ ] `try_selfhost_builder` 内で生成される tmp ハコファイル(`__BUILDER_BOX__` 版)を最小ケースで切り出し、単体で parse/実行できるように修正。
- [ ] `args` 未定義エラーや `Invalid expression` の原因となっている記述を特定し、Runner 側の `Main.main(args)` などを正しく宣言する。
- [ ] Stage3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。
- [x] Stage3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。
- [x] Stage3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako``local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。
- [x] selfhost builder/min で使っていた `fn``norm_if` クロージャ)を helper メソッドに置き換え、lambda 構文を排除。Stage3 VM が `NewClosure` を扱わなくても builder が進むようにした。
- [x] VM 側で prelude/text-merge 後の Hako コードを落とすデバッグ用トグルを追加(`NYASH_VM_DUMP_MERGED_HAKO=1` / `NYASH_VM_DUMP_MERGED_HAKO_PATH=<path>`。selfhost builder 実行時は `HAKO_SELFHOST_DUMP_MERGED_HAKO=1` / `HAKO_SELFHOST_DUMP_MERGED_HAKO_PATH=/tmp/hako_builder_merged.hako` を通じて `/tmp/hako_builder_merged.hako` にマージ後の一時ハコを保存し、`Invalid expression at line <N>` の行を直接観察できるようにする。
- 進捗メモ2025-11-15:
- `HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_SELFHOST_DUMP_MERGED_HAKO_PATH=/tmp/hako_builder_merged.hako tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako …` で 275KB の merged Hako が採取済み。`BuilderConfigBox``_is_on` など Stage3 で落ちやすい箇所を直接確認できる。
- 同条件で `apps/selfhost-runtime/runner.hako``[OK] MIR JSON written (selfhost-first)` まで到達。stage3 parse 落ちは再現していないため、次回失敗したケースが出たら `/tmp/hako_builder_merged.hako` を差し替えて行番号を追跡する。
- [ ] `try_selfhost_builder`**第一候補** とし、代表ケースlauncher.hako 等)で常にここが成功することを確認。
- [ ] `HAKO_SELFHOST_BUILDER_FIRST=1``tools/hakorune_emit_mir.sh` を叩いたときに `[OK] MIR JSON written (selfhost-first)` まで到達することをスモークで確認。
@ -63,10 +85,23 @@ Status: hotfix-in-progress緊急タスク配線修正フェーズ
- [x] `NYASH_LLVM_SKIP_BUILD=1 tools/selfhost/build_stage1.sh --out /tmp/hakorune-dev` が 0 exit すること(現状は selfhost builder を既定OFFにし、provider ルートで MIR を生成)。
- [ ] 生成された `/tmp/hakorune-dev` について:
- [ ] `./hakorune-dev emit program-json apps/selfhost-minimal/main.hako` が Program(JSON v0) を出力すること
- [ ] `./hakorune-dev emit mir-json apps/selfhost-minimal/main.hako` が MIR(JSON) を出力すること。
- [ ] `Stage1 CLI emit` 系は現在 Result:0 のみで JSON を出さないMIR が stub のためので、selfhost builder が機能するまで provider MIR を直接使う(`tools/selfhost/run_stage1_cli.sh` に JSON を書かせるタスクは後続)
- [ ] `./hakorune-dev build exe -o /tmp/hako_min apps/selfhost-minimal/main.hako` で簡単な EXE が生成され、実行して 0 exit を返すこと。
- [x] Stage1 CLI を実行する補助スクリプト `tools/selfhost/run_stage1_cli.sh` を追加し、`NYASH_NYRT_SILENT_RESULT=1` / `NYASH_DISABLE_PLUGINS=1` / `NYASH_FILEBOX_MODE=core-ro` を既定ONにした状態で CLI を呼び出せるようにしたllvmlite ハーネスと同じ JSON stdout 契約を満たすため)。
- [ ] `tools/selfhost_exe_stageb.sh` についても同様に `.hako → EXE` のスモークを通しておく(少なくとも launcher.hako / apps/selfhost-minimal/main.hako の2ケース
- 進捗メモ2025-11-15:
- `FuncScannerBox` を拡張し、`static box` メソッドを `defs` に追加(暗黙 `me` もパラメータに補完)。
- Rust 側 JSON v0 ブリッジに `ExprV0::Null` variant を追加して StageB からの Null リテラルを受理。
- `launcher.hako``while``loop` 構文へ置換し、StageB パーサ互換に揃えた。
- 依然として using 依存(例: `lang.mir.builder.MirBuilderBox`)を StageB emit が解決できず、`env.mirbuilder.emit``undefined variable: MirBuilderBox` で停止。BundleResolver / using resolver を StageB 経路に統合し、依存 Box を Program(JSON) に連結するのが次タスク。
### E. 次フェーズ25.1b)に送る selfhost builder 強化項目
- `Program.defs` を MirBuilder 側でも処理し、`HakoCli.run` / `cmd_emit_*` / `cmd_build_*` などのメソッドを MIR 関数として生成する(現状は main 1 本のみ)。
- `func_lowering` / `call_resolve` 相当の処理を Hako 側に移植し、`Call("cmd_emit_mir_json")``Global` resolved call になるようにする。
- Loop / branch / compare / Array・Map 操作など Stage1 CLI で出現するステートメントを包括的に lowering するため、`lang/src/mir/builder/internal/*` の helper を本番経路に組み込む。
- JSON 出力を `jsonfrag` ベースで構造的に生成し、functions 配列に複数関数を格納できるようにする(文字列連結のみの暫定実装を置き換える)。
- 上記を満たした段階で `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻し、Stage1 CLI バイナリの I/Ostdout JSON + exit codeを Rust/llvmlite と同じ契約に揃える。
## 25.1 / 25.1a / 25.2 の関係

View File

@ -0,0 +1,204 @@
# 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とスモーク/テストを更新する。***

View File

@ -0,0 +1,37 @@
# Phase 25.2b — Lambda / FunctionBox Semantics (Planning)
Status: planning-only将来フェーズ用のメモ実装はまだ行わない
## ゴール
- Phase 25.1b で selfhost builder の Program→MIR パリティが整ったあとに、Hakoru­ne 言語としてのラムダ式(`fn(x, y) { ... }` / `fn(x) expr`)の意味論を Rust VM / MIR / FunctionBox まで一貫させる。
- MirBuilder / selfhost コードから暫定的に排除している `fn` ベースの helper`norm_if` など)を、正式なクロージャ意味論の上に戻せるようにする。
- Stage1/Stage0 の責務分離を崩さずに、「lambda を使う Hako コード」が hv1 VM / AOT でも安全に実行できる状態を作る。
## 現状Phase 25.1b 前提)
- 構文AST:
- `docs/reference/language/LANGUAGE_REFERENCE_2025.md``fn(x, y) { ... }` / `fn(x) expr` のラムダ構文が記載済み。
- Stage3 パーサは `fn` キーワードを認識し、AST 上では Lambda ノードを持っている(`exprs_lambda.rs` 前提)。
- MIR:
- `src/mir/instruction.rs``MirInstruction::NewClosure { dst, params, body, captures, me }` が定義済み。
- `src/mir/builder/exprs_lambda.rs` は AST ラムダを `NewClosure` に降ろし、`captures` / `me` 情報を構築する実装がある。
- 実行VM:
- hv1 VM`src/backend/mir_interpreter`)は `NewClosure` / `Callee::Closure` をまだ実装していないcatch-all で InvalidInstruction
- `FunctionBox` / `ClosureEnv``src/boxes/function_box.rs` に存在し、手動で FunctionBox を作るテスト経路では動いている。
- selfhost 側:
- Phase 25.1a/b では selfhost builder から `fn` を排除し、lambda なしでも Stage1 CLI を扱える Program→MIR ルートに集中する。
## 25.2b のスコープ(案)
- Rust VM:
- `MirInstruction::NewClosure` を実装し、`FunctionBox` + `ClosureEnv` を構築する。
- `execute_callee_call``Callee::Closure` / `Callee::Value` の処理を追加し、第一級関数呼び出しをサポートする。
- Hakorune selfhost:
- selfhost builder (`MirBuilderBox` / `MirBuilderMinBox`) の helper の一部を lambda 版に戻し、`NewClosure` 経路が実際に踏まれるようにする(最初は dev トグル付きでもよい)。
- Stage1 コードでの lambda 利用ポリシー(どの層で許可するか)を docs に明記する。
- テスト:
- AST→MIR→VM で λ を含むケース単純関数、captures、`me` 利用など)をカバーする canary を追加する。
このフェーズは「selfhost builder パリティ25.1b)」完了後に着手する前提であり、本ドキュメントは計画メモのみとする。***

View File

@ -41,15 +41,28 @@ hakorune <command> [<subcommand>] [options] [-- script_args...]
## コマンド一覧MVP 案)
| コマンド | 役割 |
|-----------------------------------|-------------------------------------------|
| `run` | .hako をコンパイルして実行(既定 VM |
| `build exe` | .hako からネイティブ EXE を AOT ビルド |
| `emit program-json` | StageB で Program(JSON v0) を出力 |
| `emit mir-json` | Program(JSON) → MIR(JSON) を出力 |
| `check` | 将来の構文/型/using チェック(予約) |
| コマンド | 役割 | Phase 25.1a 実装状況 |
|-----------------------------------|-------------------------------------------|----------------------|
| `run` | .hako をコンパイルして実行(既定 VM | プレースホルダ(`[hakorune] run: not implemented yet` |
| `build exe` | .hako からネイティブ EXE を AOT ビルド | 実装済みenv.codegen経由で `.o` → EXE を生成) |
| `emit program-json` | StageB で Program(JSON v0) を出力 | 実装済み(`BuildBox.emit_program_json_v0` |
| `emit mir-json` | Program(JSON) → MIR(JSON) を出力 | 実装済み(`MirBuilderBox.emit_from_program_json_v0` |
| `check` | 将来の構文/型/using チェック(予約) | プレースホルダ(`[hakorune] check: not implemented yet` |
Phase 25.1 では、既存スクリプト置き換えに近い **`emit mir-json` / `build exe` を中心** に進め`run`/`check`設計レベルに留める
Phase 25.1a では、**`emit program-json` / `emit mir-json` / `build exe` の 3 系列のみが実働コード** であり`run` / `check`メッセージを返して終了するプレースホルダのまま運用する。CLI の出口コード90〜93やログ形式は docs と実装を同期済み
### 実装ステータスPhase 25.1a
- `.hako → Program(JSON v0)`:
- StageB (`lang/src/compiler/entry/compiler_stageb.hako`) で `BuildBox.emit_program_json_v0` を呼び出し、`"version":0,"kind":"Program"` を持つ JSON を生成。
- `Stage1UsingResolverBox``HAKO_STAGEB_MODULES_LIST` により、`using lang.mir.builder.MirBuilderBox` などの module alias を解決してから parse する。
- `Program(JSON v0) → MIR(JSON)`:
- 既定は provider 経路(`env.mirbuilder.emit`)を利用。`HAKO_MIRBUILDER_IMPORTS``using ns.Type [as Alias]` から得た alias を JSON で渡し、Rust 側ブリッジが static box 参照(`MirBuilderBox`, `BuildBox` など)を `Const(String(alias))` で生成できるようにする。
- `hostbridge` 参照は JSON ブリッジ側で well-known グローバルとして扱い、`hostbridge.extern_invoke(...)` を含む CLI コードでも `undefined variable: hostbridge` にならないようにした。
- `.hako → EXE` (`build exe`):
- `env.codegen.emit_object`MIR→.o`env.codegen.link_object`.o→EXE`hostbridge.extern_invoke` で呼び出す。
- `--quiet` でログ抑制、`-o/--out` で出力 EXE パスを指定可能。C-API トグル(`NYASH_LLVM_USE_CAPI`, `HAKO_V1_EXTERN_PROVIDER_C_ABI`)が無効な場合は fail-fast。
- Stage1 バイナリ(`target/selfhost/hakorune`)を直接叩く際は `NYASH_NYRT_SILENT_RESULT=1` を付与し、stdout に JSON だけを流す運用を徹底する(`tools/selfhost/run_stage1_cli.sh` が環境セットとバイナリ検出を担当)。
## `run` コマンド
@ -192,6 +205,24 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
`--force-jsonfrag` / `--normalize-provider` などは、引き続き設計のみで未実装。
## I/O と実行補助スクリプト
- Stage1 EXE`target/selfhost/hakorune`)は NyRTnyash_kernel上で動作するため、既定ではプログラム終了時に `Result: <code>` が stdout に追記される。
- llvmlite ハーネスとの互換性を保つため、Stage1 CLI をスクリプトから呼び出す際は `NYASH_NYRT_SILENT_RESULT=1` を常に有効化し、JSON 出力を純粋に保つ。
- 補助スクリプト: `tools/selfhost/run_stage1_cli.sh`
- 役割: Stage1 EXE の場所を解決し(既定 `target/selfhost/hakorune`)、`NYASH_NYRT_SILENT_RESULT=1` / `NYASH_DISABLE_PLUGINS=1` / `NYASH_FILEBOX_MODE=core-ro` を既定ONにしたうえで CLI 引数をそのまま渡す。
- 使い方:
```bash
tools/selfhost/run_stage1_cli.sh emit program-json apps/tests/minimal.hako
tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit mir-json apps/tests/minimal.hako
```
- 直接 EXE を叩く場合も同じ環境変数を手動で設定すること(`NYASH_NYRT_SILENT_RESULT=1 ./target/selfhost/hakorune ...`)。
これにより、stdout は JSON のみを返し、終了コードで成否を判別できるllvmlite ハーネスと同一の契約)。
- 現状の制約2025-11-15 時点):
- `lang/src/runner/launcher.hako` から生成された Program(JSON) がまだ `Main.main` のトップレベル式(`new HakoCli().run(args)`)しか含んでおらず、`HakoCli` / `BuildBox` 定義が JSON に落ちていないため、selfhost EXE 側でも `emit program-json` / `emit mir-json` の実体が欠落している。
- `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。StageB 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
- using 解決は Stage0Rust Runnerと Stage1Hakoruneの二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。
## `check` コマンド(予約)
```text
@ -239,3 +270,6 @@ hakorune check [options] <entry.hako>
- Stage1 / Stage1' の CLI インターフェース・代表挙動が一致することをゴールデン/スモークで確認する。
このファイルは「Stage1 CLI の仕様 SSOT」として扱い、実装時は本仕様を先に更新→テスト→コードの順で進める。***
- StageB using 未解決メモ:
- `lang.mir.builder.MirBuilderBox` などの `using` 依存を StageB emit が連結できておらず、`tools/selfhost/build_stage1.sh` では `undefined variable: MirBuilderBox` で停止する。BundleResolver / using resolver を StageB 経路に統合し、依存 Box 定義を Program(JSON) に含めるのが次タスク。