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:
@ -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 MIR(171 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. Stage‑B(`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 連結のどこかで Stage‑3 構文と合わない断片が残っている状態(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 に追加済みだが、
|
||||
- まだパーサが Stage‑3 構文/関数宣言の一部を受理できていない箇所があり、`Unexpected token ...` 系のエラーが残っている。
|
||||
|
||||
## Update (2025-11-16 — Stage‑B 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` を Stage‑B が参照できるようになり、Stage‑3 パーサが `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 ホットフィックスでは「Stage‑B→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`) で整理し、Stage‑3 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)` などを正しく宣言する。
|
||||
- [ ] Stage‑3 構文の使用を必要最小限に抑え、selfhost builder 用 Runner のコードをシンプルに保つ。
|
||||
- [x] Stage‑3 パーサで予約語となった `fn` をローカル変数名として使っている箇所(例: `lang/src/mir/builder/func_lowering.hako` の `local fn = func_jsons.length()`)をリネームし、`Unexpected token FN, expected identifier` を根本的に解消する。
|
||||
- [x] Stage‑3 パーサで予約語となった `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 構文を排除。Stage‑3 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` など Stage‑3 で落ちやすい箇所を直接確認できる。
|
||||
- 同条件で `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 を追加して Stage‑B からの Null リテラルを受理。
|
||||
- `launcher.hako` の `while` を `loop` 構文へ置換し、Stage‑B パーサ互換に揃えた。
|
||||
- 依然として using 依存(例: `lang.mir.builder.MirBuilderBox`)を Stage‑B emit が解決できず、`env.mirbuilder.emit` が `undefined variable: MirBuilderBox` で停止。BundleResolver / using resolver を Stage‑B 経路に統合し、依存 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/O(stdout JSON + exit code)を Rust/llvmlite と同じ契約に揃える。
|
||||
|
||||
## 25.1 / 25.1a / 25.2 の関係
|
||||
|
||||
|
||||
204
docs/development/roadmap/phases/phase-25.1b/README.md
Normal file
204
docs/development/roadmap/phases/phase-25.1b/README.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Phase 25.1b — Selfhost Builder Parity (Planning → Design Deep‑Dive)
|
||||
|
||||
Status: planning-only(実装着手前の設計・分析フェーズ)
|
||||
|
||||
## ゴール
|
||||
|
||||
- Rust 側 Program→MIR (`env.mirbuilder.emit`) と Hakorune 側 selfhost builder (`MirBuilderBox.emit_from_program_json_v0`) の機能差を埋め、Stage1 CLI(launcher.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/O(JSON stdout + exit code)を Rust/llvmlite と同じ契約に揃える。
|
||||
|
||||
## 現状(Phase 25.1a 時点)
|
||||
|
||||
### Stage‑B(Program(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` 等をファイル結合前に解決。
|
||||
- Stage‑B 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` は well‑known 変数として `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`) に閉じる。
|
||||
- Fail‑Fast:
|
||||
- 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 に落とす」ような ad‑hoc 実装は行わない:
|
||||
- PHI ノードの生成・配置は既存の LoopForm/LowerLoop 系 helper(`loop_scan_box.hako`/`lower_loop_*_box.hako` など)に一元化し、builder 本体はそれを利用する立場にとどめる。
|
||||
- LLVM harness 側の PHI 不変条件(ブロック先頭グルーピング/well‑typed incoming)を崩さない。
|
||||
- Phase 25.1b では:
|
||||
- まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/for)から対応し、
|
||||
- LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの)は `[builder/selfhost-first:unsupported:loopform]` タグなどで Fail‑Fast する。
|
||||
|
||||
## 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)をスモークで検証。
|
||||
|
||||
## 設計 TODO(FuncLoweringBox / 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 を multi‑function MIR モジュールに落とす」前提へシフト:
|
||||
- `Program.body` からはエントリ `Main.main/1` 相当の MIR 関数を生成。
|
||||
- `Program.defs` からは `HakoCli.*` などの補助関数を生成。
|
||||
- `_norm_if_apply` / `inject_funcs` の役割を整理し、「main 関数を含まない defs‑only モジュール」を返さないように Fail‑Fast する。
|
||||
|
||||
3. Fail‑Fast とデバッグ
|
||||
- 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. 検証計画
|
||||
- selfhost‑first canary:
|
||||
- `stage1_launcher_program_to_mir_canary_vm.sh` の selfhost‑first 版を追加し、`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 — Fail‑Fast・観測を揃える
|
||||
- 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-implemented(main 必須チェックはトグル付き; 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段構え」でマージする API(main 名の受け渡しなど)を追加する。
|
||||
- 成果物(現段階):
|
||||
- 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-Fast(Step 3 で LoopForm 対応を行う)。
|
||||
- 成果物:
|
||||
- Loop を含まない defs は selfhost builder で MIR 関数にできるようになり、Stage1 CLI の emit/build ハンドラの半分程度を selfhost パスで賄える。
|
||||
|
||||
### Step 3 — Loop(LoopForm)の受理
|
||||
- 目的: 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)とスモーク/テストを更新する。***
|
||||
37
docs/development/roadmap/phases/phase-25.2b/README.md
Normal file
37
docs/development/roadmap/phases/phase-25.2b/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Phase 25.2b — Lambda / FunctionBox Semantics (Planning)
|
||||
|
||||
Status: planning-only(将来フェーズ用のメモ/実装はまだ行わない)
|
||||
|
||||
## ゴール
|
||||
|
||||
- Phase 25.1b で selfhost builder の Program→MIR パリティが整ったあとに、Hakorune 言語としてのラムダ式(`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` のラムダ構文が記載済み。
|
||||
- Stage‑3 パーサは `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)」完了後に着手する前提であり、本ドキュメントは計画メモのみとする。***
|
||||
|
||||
@ -41,15 +41,28 @@ hakorune <command> [<subcommand>] [options] [-- script_args...]
|
||||
|
||||
## コマンド一覧(MVP 案)
|
||||
|
||||
| コマンド | 役割 |
|
||||
|-----------------------------------|-------------------------------------------|
|
||||
| `run` | .hako をコンパイルして実行(既定 VM) |
|
||||
| `build exe` | .hako からネイティブ EXE を AOT ビルド |
|
||||
| `emit program-json` | Stage‑B で 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` | Stage‑B で 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)`:
|
||||
- Stage‑B (`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`)は NyRT(nyash_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 が空のまま。Stage‑B 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。
|
||||
- using 解決は Stage0(Rust Runner)と Stage1(Hakorune)の二系統に分離する方針。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」として扱い、実装時は本仕様を先に更新→テスト→コードの順で進める。***
|
||||
|
||||
- Stage‑B using 未解決メモ:
|
||||
- `lang.mir.builder.MirBuilderBox` などの `using` 依存を Stage‑B emit が連結できておらず、`tools/selfhost/build_stage1.sh` では `undefined variable: MirBuilderBox` で停止する。BundleResolver / using resolver を Stage‑B 経路に統合し、依存 Box 定義を Program(JSON) に含めるのが次タスク。
|
||||
|
||||
Reference in New Issue
Block a user