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

684 lines
70 KiB
Markdown
Raw Normal View History

# Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive)
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
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 func_scan トグルのデフォルトHAKO_STAGEB_FUNC_SCAN
- 目的:
- StageB を直接叩いたときに `HAKO_STAGEB_FUNC_SCAN` を立て忘れても、`HakoCli.run``TestBox.fib` のようなメソッド定義が `Program.defs` にきちんと入るようにするselfhost builder / FuncLowering 側の前提を崩さない)。
- 実装compiler_stageb.hako 内):
- 以前: `HAKO_STAGEB_FUNC_SCAN=1` のときだけ `FuncScannerBox.scan_all_boxes` を呼び出し、それ以外は defs を生成しなかった。
- 現在: `HAKO_STAGEB_FUNC_SCAN` が未設定 (`null`) のときは既定で ON とみなし、**明示的に `"0"` が入っているときだけ OFF** として扱う。
- これにより、`tools/hakorune_emit_mir.sh` や v2 スモーク以外から StageB を直接呼び出しても、defs が常に生成される。
- 既存のテストで func_scan を無効化したいケースでは、`HAKO_STAGEB_FUNC_SCAN=0` を明示すれば従来どおり defs をスキップできる。
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
#### 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 では「multi-carrier fib などの core 小ケースについては、このラッパ経由で呼ぶ限り StageB 自体は十分に安定」とみなし、主な改善対象を Program→MIR の selfhost builder 側に置く。
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
- 手動実行時の注意:
- Stage3 ENV を立てずに StageB / VM を直接叩くと、`Undefined variable: local` のようなエラーが発生するが、これは構文/実装バグではなく「Stage3 キーワードlocal など)を Stage1 と同じルールでパースしてしまっている」ため。
- 詳細な原因と対処は `docs/development/troubleshooting/stage3-local-keyword-guide.md` にまとめてあり、selfhost 開発では「まずラッパスクリプトを使う → 必要な場合のみ ENV を明示して直叩きする」方針とする。
#### StageB と selfhost CLI canaryHakoCli.run/2の現状
- selfhost CLI の最小ケース(`tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` が生成する HakoCli.run サンプル)に対しては、修正前は StageB 実行中に VM エラー:
- `❌ VM error: Invalid value: use of undefined value ValueId(N)`%0 / 97842 / 22 などが発生し、Program(JSON v0) が 1 行としては出力されなかった(`tools/hakorune_emit_mir.sh` が Program 抽出に失敗する)。
- `NYASH_VM_VERIFY_MIR=1` を立てて `lang/src/compiler/entry/compiler_stageb.hako` を直接叩くと、修正前は StageB が生成した MIR に対して:
- `Stage1UsingResolverBox._collect_using_entries/1`
- `ParserStringUtilsBox.skip_ws/2`
- `ParserIdentScanBox.scan_ident/2`
- `ParserBox.parse_stmt2/2`
- などに `Undefined value %0 used in block ...` が多数報告されていた(詳細は `docs/private/roadmap/phases/phase-20.33/DEBUG.md` の「Invalid value: use of undefined value ValueId(N)」節を参照)。
- Task先生による Rust MIR builder 側の修正ValueId 割り当て統一loop PHI/pinned 変数の扱い修正)後は:
- `%0` / 97842 / 22 に起因する Undefined value / Invalid value エラーは `NYASH_VM_VERIFY_MIR=1` / 実行時ともに解消済み。
- pinned 変数(`__pin$*@recv` など)も loop header/exit で正しく PHI に乗るようになり、ループ後のメソッドレシーバーが未定義になる問題も再現しなくなった。
- 現時点で selfhost CLI サンプルに対して残っている課題は:
- 1) Rust VM 実行時に `❌ VM error: Invalid value: use of undefined value ValueId(17)` が発生しており、拡張済みエラーメッセージ(`fn=Main.main, last_block=Some(BasicBlockId(3419)), last_inst=Some(Call { ... ParserBox.length() [recv: %17] ... })`から「StageB Main.main 内の `ParserBox.length()` 呼び出しにおいて recv スロットValueId(17)) が定義されていない」ことが分かっている。これは verifier がまだチェックしていない「Callee.receiver 側の SSA 漏れ」であり、Phase 25.1c の StageB / LoopBuilder / LocalSSA 整理タスクで修正する前提。
- 2) `if args { ... }` まわりの truthy 判定ArrayBox を boolean 条件に使っている部分)の扱いに起因する型/意味論の揺れが残っており、こちらも 25.1c の型システム整理タスクで `ArrayBox` の truthy 規約を明文化した上で揃える想定。
- 3) `NewBox HakoCli` が plugin 前提で解決されてしまう問題は、VM 側の static box factory 統合(静的 Box を User factory にも広告する)により解消済みであり、`NYASH_DISABLE_PLUGINS=1` でも静的 Box として HakoCli を生成できるようになっているselfhost CLI canary では NewBox 自体はもはやブロッカーではない)。
- 対応方針Phase 25.1b 時点):
- BoxTypeInspector / multicarrier LoopForm 経路とは独立した **StageB/MIR 側の SSA型システムBox 解決の構造問題** として扱い、selfhost CLI canaryHakoCli.run/2 loweringはこれらが片付くまでは「25.1c の構造タスク待ち」として扱う。
- `tools/hakorune_emit_mir.sh``diagnose_stageb_failure()` は維持し、StageB の標準出力に `Invalid value: use of undefined value` が含まれている場合には `NYASH_VM_VERIFY_MIR=1``compiler_stageb.hako` 直叩き、および `docs/private/roadmap/phases/phase-20.33/DEBUG.md` への導線を表示する。加えて、VM 側の `MirInterpreter::reg_load``fn` / `last_block` / `last_inst` を含めてエラー文字列を出すようになったため、StageB 由来の undefined value は「どの関数のどの Callどの recv」で発生しているかを 1 行で特定できる。
### 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`)へフォールバックする。
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
- 現時点での不足点要約・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 する。
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
#### 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 (`mode="multi_count"`) に委譲する。
- 現段階の挙動2025-11-16 時点):
- Program(JSON v0) 内で `Loop` + `Compare` を検出し、キャリア初期値(`local a = 0; local b = 1; ...`)を 2 個以上抽出。
- `i < N``N` については、`Int` リテラルだけでなく `n` のようなパラメータ `Var` もサポートし、`limit_kind=const|param` として `LoopFormBox.build2` に伝達。
- 成功時は `[mirbuilder/internal/loop:multi_carrier:detected:limit_kind=param,value=...|param_reg=...]` タグを確実に出力し、そのまま LoopForm から返ってきた MIR を `_rebind` する。
- ループ limit が `local` 参照など selfhost で扱えない場合は `[mirbuilder/internal/loop:multi_carrier:limit:unsupported]` を出して `null` を返し、Rust provider 経路へ退避。
- スモーク:
- `tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_loopform_multi_carrier_vm.sh`
- StageB で `TestBox.fib(n)` を emit し、selfhost builder が `[funcs/basic:loop.multi_carrier]` を出したうえで `TestBox.fib/1` を含む MIR(JSON) を生成するかをチェック。
- `carriers` 長や `limit_kind=param` ログを条件に PASS/FAIL を分岐provider fallback 時は SKIP
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
- 今後の拡張:
- `LoopFormBox.build_loop_multi_carrier``limit_kind=param` 実装を一般化し(現在は param register コピー → `reg_limit` 初期化まで対応済み、break/continue 付き multi-carrier も下ろせるようにする。
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
- 代表ケースとして `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:*]` タグがそのまま表示されることを確認済み(追加改修なし)。
- Phase 25.1b 以降、`HAKO_SELFHOST_BUILDER_FIRST=1` で呼び出された場合は「StageB → selfhost builderStage1 CLI」経路のみを試行し、selfhost builder が失敗した場合は即座に非0で終了する。selfhost-first モードでは `env.mirbuilder.emit` / provider delegate へのフォールバックは行わず、MirBuilder の未整備を隠さない方針とするprovider 経路を使うときは `HAKO_SELFHOST_BUILDER_FIRST=0` を明示)。
- 成果物:
- selfhost-first で Stage1 CLI を通したときに、どの関数/構造がまだ未サポートなのかがログで推測できる状態。
#### 補足: Ny selfhost パイプラインとの関係Phase 25.1b 時点)
- `.hako → Program(JSON v0) → MIR(JSON)` のメイン経路は、StageB`compiler_stageb.hako`)と MirBuilder/selfhost builder で完結させる。Ny selfhost`src/runner/selfhost.rs`)は `.ny` 用の補助ルートとして扱い、Phase 25.1b のスコープからは外す。
- `NYASH_USE_NY_COMPILER`:
- Phase 25.1b で「明示 opt-in」既定=0に変更。Runner は `NYASH_USE_NY_COMPILER=1` が立っているときだけ selfhost パイプラインNy→JSON v0を試行し、それ以外では従来どおり Rust parser/JSON v0 bridge を使う。
- `.hako` 実行や `tools/hakorune_emit_mir.sh` 経由の StageB/MirBuilder/selfhost builder には影響しない(これらは `NYASH_USE_NY_COMPILER=0` / `NYASH_DISABLE_NY_COMPILER=1` で起動)。
- Python MVP:
- `tools/ny_parser_mvp.py` は Phase 15 時点の Ny→JSON v0 実験用ハーネスであり、Phase 25.1b では `NYASH_NY_COMPILER_USE_PY=1` のときだけ有効にする。
- 既定では Ny selfhost パイプラインから Python には落ちない(脱 Python 方針に合わせて dev 専用の補助線に格下げ)。
- inline selfhost compiler`inline_selfhost_emit.hako`:
- `try_run_selfhost_pipeline` の最終手段として、`using lang.compiler.parser.box as ParserBox` / `using lang.compiler.stage1.emitter_box as EmitterBox` を含む小さな Hako を生成し、`ParserBox.parse_program2``EmitterBox.emit_program` で JSON v0 を得る経路が残っている。
- 現状、この inline 経路は `.ny` の大きなソースに対して 60s タイムアウト+ `Undefined variable: local` を伴うことがあり、ParserBox/Stage3/using 周りに無限ループ相当のバグが残っている疑いがある。
- Phase 25.1b では `.hako` selfhost builder から Ny selfhost 経路を切り離すことを優先し、inline 経路のバグは `[ny-compiler] inline timeout ...` `[ny-inline:hint]`stdout/stderr の head を添えるで可視化したうえで、後続フェーズ25.1c 以降)の構造タスクとして扱う。
### 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で通せる基礎が整った。
- 追加アップデート2025-11-16: multi-carrier ループ(`TestBox.fib(n)` など)も `LowerLoopMultiCarrierBox``LoopFormBox.build_loop_multi_carrier` 経由で selfhost lowering できるようになり、limit が `Int` でなく `Var(n)` でも `[mirbuilder/internal/loop:multi_carrier:detected:limit_kind=param,...]` を出して処理できる。
- 次のステップ: LoopForm対応の動作確認スモークテスト追加、Step4MethodCall/ExternCallへ進む。
#### Step 3.1 — Box 型情報 APIRust Parity★New
- 背景:
- Stage3 VM では `"" + MapBox` のような「Box を文字列に暗黙変換する演算」が禁止されており、既存の `JsonEmitBox` / `BoxHelpers``LoopOptsBox.build2` から呼び出される)が `repr` 判定に依存しているため multi-carrier の JSON 生成が `Type error` で停止した。
- Rust 側の MirBuilder は enum で型が決まっており `match` で分岐できる。Hakorune 側でも同等の「Box の種別を問い合わせる API」を用意して文字列ハックを撤廃する必要がある。
- 設計方針:
1. `lang/src/shared/common/box_type_inspector_box.hako` を追加し、`BoxTypeInspectorBox.kind(value)` / `is_map(value)` / `is_array(value)` 等の API を提供する。
2. 実装は Stage0 Rust 側に `env.box_introspect(kind, value)` 的な extern を追加し、`hostbridge.extern_invoke("env.box_introspect","kind",[value])` で種別名(例: `"MapBox"`, `"ArrayBox"`, `"Int"`)を返す。
3. `BoxHelpers` / `JsonEmitBox` / `LoopOptsBox` など、Box 種別チェックが必要な箇所はすべてこの API に置き換え、`"" + value` を一切使わない。
4. 返り値は最小で `Null/Bool/Int/String``MapBox/ArrayBox/HostHandleBox`Stage1 で使用する型)をカバーし、将来的に `type_id` などを拡張する。
- 追加で行うこと:
- `CURRENT_TASK.md` に Box 型 API 実装タスクを追加し、LoopForm multi-carrier の JSON 出力がこの API 依存であることを明示。
- Stage0 側での対応(`env.box_introspect` 新規 externの設計も合わせて `phase-25.1b/README.md` に記述しておくSelfhost 側で API 追加→Rust 側 stub→VM 反映の順)。
- 現状2025-11-16 時点): Stage3 VM 経路で `BoxTypeInspectorBox.kind` / `is_map` / `is_array` が MapBox / ArrayBox を正しく認識し、小さな Hako テストで `hostbridge.extern_invoke("env.box_introspect","kind",[value])``env.box_introspect.kind` provider → plugin loader v2 の BoxIntrospect 実装までが endtoend で動作することを確認済み。
- fib multicarrier 経路と selfhost multicarrier smoke 用の canary ケース(`tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_loopform_multi_carrier_vm.sh`は、20251116 時点で `env.box_introspect.kind` provider 経路BoxTypeInspector 経由の multi-carrier LoopForm で PASS 済み。ログに `[mirbuilder/internal/loop:multi_carrier:detected:limit_kind=param,...]``[funcs/basic:loop.multi_carrier] -> TestBox.fib/1` が現れ、出力 MIR(JSON) に `"name":"TestBox.fib/1"` が含まれることを確認したため、「env.box_introspect.kind provider 経路完了 / multicarrier selfhost-first canary PASS」とみなす。
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
### 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 に退避できるようにする。
#### Step 4.1 — Rust 層 Call/ExternCall 契約の整理(移植元 SSOT
- 目的:
- Stage1 側の MethodCall/ExternCall lowering を「Rust 実装の振る舞い」に正確に揃えるため、Rust 層の Call/ExternCall/hostbridge 経路を SSOT として整理しておく。
- ここでの整理は構造レベルに留め、意味論の“拡張”は行わないHako 側はこの契約に従うだけ)。
- Rust 側のコア断面(ざっくり構造):
- **MIR ビルダ(呼び出し生成)**:
- `src/mir/builder/builder_calls.rs`
- `emit_unified_call(dst, CallTarget, args)`:
- `CallTarget::Method { box_type, method, receiver }``Callee::Method` を作り、`MirInstruction::Call { callee: Some(Callee::Method{..}), ... }` を emit。
- `CallTarget::Extern(name)` → 文字列 `"env.codegen.emit_object"` などを `ExternCall` に変換(`iface_name="env.codegen"`, `method_name="emit_object"`)。
- `CallTarget::Global(name)``Callee::Global(name)` 付き `Call` を emit`execute_global_function` へ)。
- **VM 側 Call ハンドラ**:
- `src/backend/mir_interpreter/handlers/calls/global.rs`:
- `execute_global_function(func_name, args)`:
- まず `functions` テーブルにあれば module 内関数として実行。
- そうでない場合、`normalize_arity_suffix("name/1")` した base 名に対して:
- `"print"``execute_extern_function("print", args)`
- `"hostbridge.extern_invoke"``execute_extern_function("hostbridge.extern_invoke", args)`SSOT: hostbridge 経由の extern は必ずここを通る)。
- `"env.mirbuilder.emit"` / `"env.codegen.emit_object"` / `"env.codegen.link_object"`:
- それぞれ `crate::host_providers::{mir_builder,llvm_codegen}` を直接呼ぶ「グローバル関数版」ルート。
- `src/backend/mir_interpreter/handlers/calls/externs.rs`:
- `execute_extern_function(iface, method, args)`:
- `("env.mirbuilder","emit")` / `("env.codegen","emit_object")` / `("env.codegen","link_object")` などを `extern_provider_dispatch` に委譲。
- `"hostbridge.extern_invoke"` base 名もここから `extern_provider_dispatch("hostbridge.extern_invoke", args)` に流す。
- **ExternCall / hostbridge.extern_invoke の provider**:
- `src/backend/mir_interpreter/handlers/externals.rs`:
- ExternCall 形(`MirInstruction::ExternCall`) を `iface_name`,`method_name` ごとに振り分け:
- `("env.mirbuilder","emit")``extern_provider_dispatch("env.mirbuilder.emit", args)`
- `("env.codegen","emit_object")``extern_provider_dispatch("env.codegen.emit_object", args)`
- `("env.codegen","link_object")` → 第3引数 ArrayBox `[obj_path, exe_out?]` を取り出して C-API ルートへ。
- `("hostbridge","extern_invoke")``extern_provider_dispatch("hostbridge.extern_invoke", args)`(なければ Invalid
- `src/backend/mir_interpreter/handlers/extern_provider.rs`:
- `extern_provider_dispatch(key, args)`:
- `"env.mirbuilder.emit"`:
- `args[0]``program_json` にし、`HAKO_MIRBUILDER_IMPORTS` から imports マップを読む。
- `host_providers::mir_builder::program_json_to_mir_json_with_imports` を呼んで MIR(JSON) 文字列を返す。
- `"env.codegen.emit_object"`:
- `args[0]` を MIR(JSON) 文字列にして v1 へ normalize → `llvm_codegen::mir_json_to_object`
- `"env.codegen.link_object"`:
- `args[0]`=obj_path, `args[1]`=exe_out を文字列化し、C-API ルート(`NYASH_LLVM_USE_CAPI=1` + `HAKO_V1_EXTERN_PROVIDER_C_ABI=1`)で `link_object_capi`
- `"env.get"` / `"env.box_introspect.kind"` / `"hostbridge.extern_invoke"` もここで扱うBoxIntrospect は plugin_loader_v2 に委譲)。
- plugin_loader v2 側の env.*:
- `src/runtime/plugin_loader_v2/enabled/extern_functions.rs`:
- `extern_call(iface_name, method_name, args)``env.*` を一括処理。
- `handle_mirbuilder("emit", args)`:
- `args[0]` の Program(JSON v0) 文字列を受け取り、`host_providers::mir_builder::program_json_to_mir_json` で MIR(JSON v0) を返す。
- `handle_codegen("emit_object", args)`:
- `args[0]` の MIR(JSON v0) 文字列を受け取り、ny-llvmc ラッパ (`llvm_codegen::mir_json_to_object`) で object (.o) のパスを返す。
- BridgeJSON v0 → MIRの特別扱い:
- `src/runner/json_v0_bridge/lowering/expr.rs` / `lowering.rs`:
- `MapVars::resolve`:
- `hostbridge` / `env` を特殊変数として扱い、それぞれ Const(String) `"hostbridge"` / `"env"` を生成するMethod チェーンを降ろすためのプレースホルダ)。
- `me` については、Bridge 環境の `allow_me_dummy` が ON のときだけ NewBox を注入する(通常は JSON defs 側で明示パラメータとして扱う)。
- `lower_expr_with_scope`:
- `ExprV0::Extern { iface, method, args }``MirInstruction::ExternCall { iface_name, method_name, ... }`
- `ExprV0::Method` の特別ケース:
- `ConsoleBox``print/println/log``ExternCall env.console.log`
- `env.box_introspect.kind(value)` パターン → `ExternCall env.box_introspect.kind` に正規化。
- defs 降下(`lowering.rs`:
- JSON v0 の `defs` に対して、`box_name != "Main"` の関数を **インスタンスメソッド** とみなし、
- `signature.params` に「暗黙 `me` + 明示パラメータ」を載せる。
- `func_var_map``me``func.params[0]` を、残りのパラメータ名を `params[1..]` にバインドする。
- これにより StageB / Stage1 側で `_build_module_map()` のような「params: [] だが `me` を使う」メソッドでも、
Rust VM 実行時に `me` 未定義にならず、BoxCall が正しく解決されるようになった。
- Selfhost への移植指針Rust SSOT に沿った箱設計):
- `MethodCall`:
- Hako 側では「どの Box のどのメソッドを MIR の `mir_call(Method)` に落とすか」を Box 単位の helper で管理する(`LoopOptsBox``Cli*Box` と同様に)。
- Rust 側の `CallTarget::Method``Callee::Method` の変換ルールreceiver レジスタの扱い、box_name/method 名)を Step 4 の設計メモと揃える。
- `ExternCall`:
- `hostbridge.extern_invoke("env.codegen","emit_object"/"link_object", args)``env.mirbuilder.emit` などは、
- Rust では最終的に `ExternCall``extern_provider_dispatch("env.*", args)``plugin_loader_v2::extern_call("env.*", method, args)` / `host_providers::*` という構造になっている。
- Hako 側では「env 名+メソッド名の組(= key」を列挙した薄い `*BridgeBox` でラップし、そのうえで `ExternCallLowerBox``externcall func="env.codegen.emit_object"` を emit する。
- 未対応の name/method 組は必ず Fail-Fastタグ付きで provider に回す。
この Step 4.1 を「Rust 側の SSOT」として固定しておき、Phase 25.1c 以降ではこの契約に沿って Hako 側の MethodCall/ExternCall lowering 箱を実装・整理していくRust 側に新ルールは追加しない)方針とする。
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
- 実装イメージ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 エントリを含むか」を把握できるようにする(観測専用)。
- 作業:
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
- `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でも可
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
作業順は 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 プロファイルへの昇格を検討する。
***