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>
This commit is contained in:
nyash-codex
2025-11-16 03:11:49 +09:00
parent 8ffc4d0448
commit 5f06d82ee5
19 changed files with 1700 additions and 14 deletions

View File

@ -106,7 +106,10 @@ Update (2025-11-15 — Phase 25.1a: Stage1 build pipeline hotfix progress)
- JSON 出力を jsonfrag ベースで構築し、functions 配列に複数関数を格納可能にする。 - JSON 出力を jsonfrag ベースで構築し、functions 配列に複数関数を格納可能にする。
- 上記の完了後、selfhost-first を既定に戻し Stage1 CLI EXE の JSON stdout 契約Rust/llvmlite と同等)を仕上げる。 - 上記の完了後、selfhost-first を既定に戻し Stage1 CLI EXE の JSON stdout 契約Rust/llvmlite と同等)を仕上げる。
- ループについては LoopForm 正規化を前提とし、LoopForm の制約を満たさない形キャリア3変数以上・順序再配置不能などは selfhost builder では扱わず、タグ付き FailFast で検知するPHI ノード生成は既存 LoopForm/LowerLoop helper に一元化)。 - ループについては LoopForm 正規化を前提とし、LoopForm の制約を満たさない形キャリア3変数以上・順序再配置不能などは selfhost builder では扱わず、タグ付き FailFast で検知するPHI ノード生成は既存 LoopForm/LowerLoop helper に一元化)。
- docs: `docs/development/roadmap/phases/phase-25.1b/README.md` を design deepdive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針FailFast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序などを整理済み。Step0FailFast/観測導線)は 2025-11-15 実装済みdefs_only/no_match タグ+ `_lower_func_body` トレース、Step1 は `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` トグルによる main 必須チェックを inject_funcs に入れるところまで完了。Step2 では `FuncBodyBasicLowerBox` を追加して Local/If/Return パターンを既存の minimal lowers で箱化し、`_lower_func_body` から段階的に呼び出せるようにした。Step3LoopForm対応も 2025-11-15 実装完了:`FuncBodyBasicLowerBox._try_lower_loop` から `LowerLoopSumBcBox`/`LowerLoopSimpleBox` を呼び、LoopForm正規化済みループをMIRに落とす導線を確立、PHI/キャリア処理は既存Loop lowersに完全委譲、制約外は `[builder/funcs:unsupported:loopform]` でFail-Fast。*** - docs: `docs/development/roadmap/phases/phase-25.1b/README.md` を design deepdive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針FailFast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序などを整理済み。Step0FailFast/観測導線)は 2025-11-15 実装済みdefs_only/no_match タグ+ `_lower_func_body` トレース、Step1 は `HAKO_MIR_BUILDER_REQUIRE_MAIN=1` トグルによる main 必須チェックを inject_funcs に入れるところまで完了。Step2 では `FuncBodyBasicLowerBox` を追加して Local/If/Return パターンを既存の minimal lowers で箱化し、`_lower_func_body` から段階的に呼び出せるようにした。Step3LoopForm対応も 2025-11-15 実装完了:`FuncBodyBasicLowerBox._try_lower_loop` から `LowerLoopSumBcBox`/`LowerLoopSimpleBox` を呼び、LoopForm正規化済みループをMIRに落とす導線を確立、PHI/キャリア処理は既存Loop lowersに完全委譲、制約外は `[builder/funcs:unsupported:loopform]` でFail-Fast。Step4MethodCall/ExternCall パリティ)は Rust 層 (`builder_calls.rs` / extern handler) と StageB Program(JSON) の形を読み解いた設計メモに基づき、`FuncBodyBasicLowerBox._try_lower_return_method` による ArrayBox.size/getparams ベース receiverと StringBox.length引数なしの最小カバーを実装しつつ、新箱 `ExternCallLowerBox``lang/src/mir/builder/func_body/extern_call_box.hako`)で `Return(hostbridge.extern_invoke(\"env.codegen\",\"emit_object\"|\"link_object\", Var(arg)))``externcall env.codegen.emit_object|link_object``ret` に落とす経路を追加した。`FuncLoweringBox._lower_func_body` から BasicLowerBox の次に ExternCallLowerBox を呼ぶよう配線済み。Step1/Step2 の一環として `CliEntryLowerBox` / `CliRunShapeScannerBox` / `CliRunLowerBox` を追加し、Stage1 CLI エントリ(`Main.main``HakoCli.run`)と `HakoCli.run` の JSON 形状をタグ付きで観測できるようにした。`CliRunLowerBox` には `_check_shape``_emit_mir` を実装し、MVP 用の `HakoCli.run`argc 初期化・args.size/get・run/build/emit/check 4分岐・unknown=2のみを `HAKO_MIR_BUILDER_CLI_RUN=1` ガード付きで selfhost builder から MIR に降ろせるようにしたStage1 本番の run にはまだ適用していない)。***
- LoopForm 複雑ケースについては、「Rust 側の LoopForm/PHI 実装をオラクルとし、同じ .hako を provider-first と selfhost-first の両方で通す」方針を docs に追記。LoopForm/PHI のロジック自体は Hako 側では `lower_loop_*_box.hako` 群に閉じ込め、FuncLowering/MirBuilder 本体は `_rebind` 呼び出しのみを行う。Rust は StageB/MIR→LLVM の検証と既存 provider 経路の「正解」としてだけ使う。
- 新しい LoopForm 用 helper 箱 `lower_loop_multi_carrier_box.hako` を追加し、multi-carrier ループfib 風)を Program(JSON v0) 上で検出して `[mirbuilder/internal/loop:multi_carrier:detected]` タグを出すだけの足場を用意Phase 25.1b ではまだ LoopFormBox への委譲は行わず、Fail-Fast ポリシーを維持)。
- スモーク構成について、「Rust builder 用スモークと selfhost builder 用スモークをペアで置く」ルール(`*_provider_vm.sh` / `*_selfhost_vm.sh` 命名、同じ .hako を共有)を phase-25.1b README に記載。今後は loop/method/extern/Stage1 CLI 用の selfhost-first canary を `phase251` 配下に追加していく。
Update (2025-11-16 — StageB using resolver alias 化 & 環境導線) Update (2025-11-16 — StageB using resolver alias 化 & 環境導線)
- StageB entry (`compiler_stageb.hako`) の `using "hako.compiler.entry.bundle_resolver"` / `using "lang/src/compiler/entry/using_resolver_box.hako"` を module alias (`hako.compiler.entry.bundle_resolver`, `lang.compiler.entry.using_resolver`) に置き換え、Stage3 パーサが string literal using を弾いていた問題を解消。 - StageB entry (`compiler_stageb.hako`) の `using "hako.compiler.entry.bundle_resolver"` / `using "lang/src/compiler/entry/using_resolver_box.hako"` を module alias (`hako.compiler.entry.bundle_resolver`, `lang.compiler.entry.using_resolver`) に置き換え、Stage3 パーサが string literal using を弾いていた問題を解消。

View File

@ -1,6 +1,6 @@
# Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive) # Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive)
Status: planning-only実装着手前の設計・分析フェーズ Status: Step0〜3 実装済み・Step4Method/Extern実装フェーズ
## ゴール ## ゴール
@ -19,6 +19,15 @@ Status: planning-only実装着手前の設計・分析フェーズ
- `Stage1UsingResolverBox``lang/src/compiler/entry/using_resolver_box.hako` `HAKO_STAGEB_MODULES_LIST``nyash.toml``[modules]` を参照し、`using lang.mir.builder.MirBuilderBox` 等をファイル結合前に解決。 - `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 entry 側は string literal using を廃止し、`using lang.compiler.entry.using_resolver as Stage1UsingResolverBox` のように module alias を使用する。
#### StageB の安定度と使用上の注意
- 正規経路:
- StageB は `tools/hakorune_emit_mir.sh` / `tools/selfhost/selfhost_build.sh` 経由で呼び出すことを前提としており、これらのラッパが Stage3 用 ENV`NYASH_PARSER_STAGE3=1` / `HAKO_PARSER_STAGE3=1` / `NYASH_PARSER_ALLOW_SEMICOLON=1` など)を一括でセットする。
- Phase 25.1b では「このラッパ経由で呼ぶ限り StageB 自体は安定」とみなし、主な改善対象を Program→MIR の selfhost builder 側に置く。
- 手動実行時の注意:
- Stage3 ENV を立てずに StageB / VM を直接叩くと、`Undefined variable: local` のようなエラーが発生するが、これは構文/実装バグではなく「Stage3 キーワードlocal など)を Stage1 と同じルールでパースしてしまっている」ため。
- 詳細な原因と対処は `docs/development/troubleshooting/stage3-local-keyword-guide.md` にまとめてあり、selfhost 開発では「まずラッパスクリプトを使う → 必要な場合のみ ENV を明示して直叩きする」方針とする。
### Rust provider (`env.mirbuilder.emit`) ### Rust provider (`env.mirbuilder.emit`)
- `program_json_to_mir_json_with_imports`: - `program_json_to_mir_json_with_imports`:
@ -47,6 +56,10 @@ Status: planning-only実装着手前の設計・分析フェーズ
- どの `Lower*Box` もマッチしないが `func_defs_mir` が非空の場合は、`"{\"functions\":[" + defs + "]}"` という最小モジュールを組み立てて `_norm_if_apply` に渡す。 - どの `Lower*Box` もマッチしないが `func_defs_mir` が非空の場合は、`"{\"functions\":[" + defs + "]}"` という最小モジュールを組み立てて `_norm_if_apply` に渡す。
- このケースでは main 関数を含まない defs だけの MIR モジュールになり、ny-llvmc 側でエントリポイント不足や空挙動 EXE を生む原因になる。 - このケースでは main 関数を含まない defs だけの MIR モジュールになり、ny-llvmc 側でエントリポイント不足や空挙動 EXE を生む原因になる。
- `func_defs_mir` も空で internal lowers も不発の場合は `null` を返し、最後に provider delegate`env.mirbuilder.emit`)へフォールバックする。 - `func_defs_mir` も空で internal lowers も不発の場合は `null` を返し、最後に provider delegate`env.mirbuilder.emit`)へフォールバックする。
- 現時点での不足点要約・2025-11-16:
- `FuncBodyBasicLowerBox` が本番でカバーできているのは、Local/If/Return の基本形LoopForm 正規化済み Loopごく一部の MethodCall`args.size/get``String.length` の Return 形に限られる。Stage1 CLI のような複雑な関数本体(複数 LocalIf ネストLoopMethod/Extern 混在)は、ほぼすべて Rust provider 経路にフォールバックしている。
- ExternCall は `hostbridge.extern_invoke("env.codegen","emit_object"|"link_object", arg)``ExternCallLowerBox` で最小サポートしているだけで、それ以外の extern`env.mirbuilder.emit` や console 系など)は現状 selfhost builder の対象外。
- `HakoCli.run` 専用 lower`CliRunLowerBox`)は MVP 用のシンプルな run パターンのみを想定しており、実際の `launcher.hako` の runusage/unknown ログ付き)は shape mismatch で selfhost 降ろしの対象外。ここを Stage1 実形に合わせて広げることが Phase 25.1b の中心タスクになっている。
### Stage1 EXE / build_stage1.sh の現状 ### Stage1 EXE / build_stage1.sh の現状
@ -91,6 +104,36 @@ Status: planning-only実装着手前の設計・分析フェーズ
- まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/forから対応し、 - まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/forから対応し、
- LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの`[builder/selfhost-first:unsupported:loopform]` タグなどで FailFast する。 - LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの`[builder/selfhost-first:unsupported:loopform]` タグなどで FailFast する。
#### LoopForm 複雑ケースへの拡張方針Rust builder をオラクルに使う)
- ねらい:
- 複雑な LoopFormキャリア複数・条件付き更新などについては、**Rust 側 MirBuilder/LoopForm 実装を「正解(オラクル)」として扱い**、Hakorune 側の `LowerLoop*Box` 群をそれに追従させる。
- Hakorune 側は LoopForm の設計や PHI 配線を再実装せず、「入力 JSON のパターンマッチ+既存 LowerLoop* の呼び出し」に専念する。
- 手順イメージ:
1. Rust 側 loop スモーク(例: `docs/development/roadmap/phases/phase-17-loopform-selfhost/``phase-21.6/21.8` 関連)に対応する .hako を特定し、provider-first`HAKO_SELFHOST_BUILDER_FIRST=0`)で MIR(JSON) を採取する。
2. 同じ .hako を selfhost-first`HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_MIR_BUILDER_FUNCS=1 HAKO_SELFHOST_TRACE=1`)で通し、`LowerLoop*Box` がどこまで拾えているか/どのケースが `[builder/funcs:unsupported:loopform]` になっているかを観測する。
3. 差分が出ているループだけを対象に、小さな `LowerLoopXXXBox`(または既存 LowerLoop* の強化)を追加する。
4. ループの意味論差異キャリア更新・退出条件・rcが出ていないかは、VM/EXE canaryrc チェック)で確認する。
- ガード:
- 新しい LoopForm 対応はすべて既存 `lower_loop_*_box.hako` 群の中に閉じ込め、FuncLowering/MirBuilder 本体では依然として「LoopForm 結果を `_rebind` で名前付けするだけ」にとどめる。
- Rust 側の LoopForm/PHI 実装を変えずに selfhost 側のカバー率だけを上げるのが Phase 25.1b の範囲。
#### 25.1b で追加する LoopForm 用の新箱(足場)
- `lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako`
- 目的:
- Fibonacci 風の「multi-carrier」ループ`i,a,b,t` など複数のキャリアを持つループ)を selfhost builder 側で検出し、将来的に LoopFormBox に委譲できるようにする足場。
- 現段階の挙動:
- Program(JSON v0) 内に
- `type:"Loop"``type:"Compare"` が存在し、
- ループ本体に `type:"Local"` が複数存在する(=キャリア候補が複数ある)
ことを確認したら、`HAKO_MIR_BUILDER_TRACE=1` 相当builder_config.trace_enabled有効時に
`[mirbuilder/internal/loop:multi_carrier:detected]` タグを出す。
- まだ LoopFormBox への `build2` 委譲は行わず、`null` を返して Rust provider 経路にフォールバックするFail-Fast ポリシー維持)。
- 今後の拡張:
- `LoopScanBox` を使ってキャリア変数群・limit などを抽出し、`LoopOptsBox.build2(opts)` に渡すロジックを追加する。
- 代表ケースとして `tools/smokes/v2/profiles/quick/core/vm_loop_phi_multi_carriers.sh` と同型の .hako を selfhost-first で通す canary を追加し、VM/EXE rc を Rust オラクルと比較する。
## Next Steps実装フェーズに入るときの TODO ## Next Steps実装フェーズに入るときの TODO
1. 調査: 1. 調査:
@ -191,13 +234,228 @@ Status: planning-only実装着手前の設計・分析フェーズ
- `cmd_build_exe``loop(i < argc)`等、Stage1 CLIの代表的なwhile/forパターンをselfhost builderで通せる基礎が整った。 - `cmd_build_exe``loop(i < argc)`等、Stage1 CLIの代表的なwhile/forパターンをselfhost builderで通せる基礎が整った。
- 次のステップ: LoopForm対応の動作確認スモークテスト追加、Step4MethodCall/ExternCallへ進む。 - 次のステップ: LoopForm対応の動作確認スモークテスト追加、Step4MethodCall/ExternCallへ進む。
### Step 4 — MethodCall / ExternCall パリティ ### Step 4 — MethodCall / ExternCall パリティ設計メモ・Rust層読解込み
- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現。 - Status: design-onlyRust 層の挙動を踏まえた設計まで)
- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現し、Rust 側の `build_method_call` / extern handler と意味論を揃える(ただしスコープは Stage1 必要最小限に限定)。
- 対象Phase 25.1b で扱う範囲に限定):
- Stage1 CLI (`lang/src/runner/launcher.hako`) 内で出現する代表パターン:
- `FileBox` 系: `fb.open(path,"r")` / `fb.read()` / `fb.write(content)` / `fb.close()`
- `ArrayBox` 系: `args.size()` / `args.get(i)` / `args.push(v)`
- `MapBox` 系(必要になれば): `m.get(k)` / `m.set(k,v)` / `m.size()`
- `String` 系: `s.length()`(現状 Step2 でも使われている)
- self-call: `me.cmd_emit_program_json(args)` / `me.cmd_emit_mir_json(args)` / `me.cmd_build_exe(args)` など
- ExternCall 的なもの: `hostbridge.extern_invoke("env.codegen","emit_object",args)` / `hostbridge.extern_invoke("env.codegen","link_object",args)`
- 設計方針:
1. **MethodCall → mir_call(Method)**box メソッド呼び出し)
- StageB Program(JSON v0) での形(実測):
- `return arr.size()` は defs 内で次のように現れる:
```json
{"type":"Local","name":"arr","expr":{"type":"New","class":"ArrayBox","args":[]}}
{"type":"Return","expr":{
"type":"Method",
"recv":{"type":"Var","name":"arr"},
"method":"size",
"args":[]
}}
```
- `hostbridge.extern_invoke("env.codegen","emit_object", a)` は:
```json
{"type":"Expr","expr":{
"type":"Method",
"recv":{"type":"Var","name":"hostbridge"},
"method":"extern_invoke",
"args":[
{"type":"Str","value":"env.codegen"},
{"type":"Str","value":"emit_object"},
{"type":"Var","name":"a"}
]
}}
```
- FuncLoweringBox / FuncBodyBasicLowerBox 側で扱う基本パターン:
- `Return(Method recv.method(args))` を検出し、
- `recv` が **パラメータ由来の Var**(例: `args.size()`)のときだけ selfhost lowering 対象にする。
- ローカル由来(`local fb = new FileBox(); return fb.read()`)は Phase 25.1b では対象外とし、今後のフェーズでローカルテーブル導入後に扱う。
- 引数は Int/Var/String のみ対象lambda や複合式は未対応)。
- MIR 側では `mir_call` の Method 形を使う:
```json
{"op":"mir_call","dst":R,
"mir_call":{
"callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":recv_reg},
"args":[ /* 必要に応じて追加 */ ],
"effects":[]
}}
```
- `box_name` / `method` は whitelisted な Box のみ(当面は `ArrayBox` と `String` 程度)をハードコードで対応。
- receiver は `receiver` フィールド(または args の先頭でレジスタ番号を渡し、Rust 側の `mir_call` 仕様と揃える。
2. **self-callme.cmd_*)の解決**
- Stage1 CLI の `me.cmd_*` は、StageB の FuncScanner から `Program.defs` に `box:"HakoCli", name:"cmd_*"` として載る。
- これを FuncLoweringBox の `func_map` に登録済みなので、
- `Call("cmd_emit_mir_json", args)` → `func_map("cmd_emit_mir_json") = "HakoCli.cmd_emit_mir_json"`
- という形で Global 関数名を解決できる。
- Step4 では `Return(Call("cmd_*", ...))` だけでなく、「単独の Call 文」や「MethodCall 内からの self-call」も対応させる余地があるが、
- Phase 25.1b ではまず `Return(Call(...))` パターンの範囲内で self-call を `Box.method/N` に揃えるところまでに留める(広げるのは後続フェーズ)。
3. **ExternCallhostbridge.extern_invokeの扱い**
- Rust 側では `hostbridge.extern_invoke("env.codegen","emit_object",args)` 等を特別扱いし、C-ABI 経由で `env.codegen` provider にルーティングしている。
- selfhost builder 側では、Stage1 CLI の以下のパターンのみをサポート対象とする:
- `"env.codegen","emit_object",[mir_json]`
- `"env.codegen","link_object",[obj_path,(out_path)]`
- `"env.mirbuilder","emit",[program_json]`(必要なら)
- JSON 上では `expr.type:"Method"` `recv:Var("hostbridge")` `method:"extern_invoke"` で表現されているので、
- `args[0]` / `args[1]` が `"env.codegen"`, `"emit_object"` or `"link_object"` であることを確認し、
- static なパターンマッチで MIR の `extern_call` に落とす:
```json
{"op":"externcall","func":"env.codegen.emit_object","args":[ /* regs */ ]}
```
- ここでは「すべての extern を一般化する」のではなく、Stage1 CLI が実際に使っている env 名とメソッド名だけを point fix するRust Freeze Policy に従い、意味論は Rust 版を真似るが範囲は狭く保つ)。
4. **未対応パターンの Fail-Fast**
- MethodCall/ExternCall の lowering 中に、
- 複雑なオブジェクト式(ネストした MethodCall/Array/Map リテラルなど)、
- 引数に対応していない型lambda など)、
- 未サポートの env 名 / メソッド名(`env.codegen` 以外)、
が見つかった場合は、`[builder/funcs:unsupported:call]` タグを出して `null` で戻る。
- これにより、「知らない形をなんとなく MIR にする」ことを避け、Rust provider や legacy CLI delegate に退避できるようにする。
- 実装イメージPhase 25.1b 中にやるときの TODO:
1. `FuncLoweringBox` に小さな helper を追加:
- `_lower_method_call(body_json, func_name, box_name, params_arr)` → MethodCall パターン検出+`mir_call Method` 生成。
- `_lower_hostbridge_extern(body_json, ...)` → env.codegen/env.mirbuilder 用の最小 ExternCall 生成。
2. `_lower_func_body` の冒頭か、既存 `Return(Call)` の前後でこれら helper を呼び出し、マッチした場合のみ MIR を返す。
3. Tag/ログ:
- `HAKO_SELFHOST_TRACE=1` 時に `[funcs/basic:method.*]` / `[funcs/basic:extern.*]` trace を出し、どの defs が Method/Extern 経由で lowering されたか観測できるようにする。
4. スモーク:
- `tools/smokes/v2/profiles/quick/core/phase251` に `selfhost_mir_methodcall_basic_vm.sh` のような canary を追加し、
- ArrayBox.push / FileBox.open/read/write / env.codegen.emit_object/link_object の代表ケースを selfhost builder-first で通し、
- `mir_call` / extern call が出力 MIR に含まれていることを確認する。
### Step 1 — CLI entry detection (CliEntryLowerBox)
- 目的: Stage1 CLI の入口構造(`Main.main` → `HakoCli.run`)を Program(JSON v0) 上で検出し、selfhost builder が「どの Program が CLI エントリを含むか」を把握できるようにする(観測専用)。
- 作業: - 作業:
- `FuncLoweringBox` に MethodCall/ExternCall の lowering ルートを追加し、`mir_call Method` もしくは `extern_call` を生成 - `lang/src/mir/builder/func_body/cli_entry_box.hako` に `CliEntryLowerBox` を追加
- `func_map` により `me.cmd_build_exe` のような self-call も Box.method/N に resolve未対応の呼び出しは `[builder/funcs:unsupported:call]` で Fail - `scan(program_json)` で以下を確認し、すべて満たすときだけタグを出す:
- 成果物: - `Program.body` 内に `New(HakoCli)` 相当の JSON`"class":"HakoCli"`)が存在する。
- Stage1 CLI の主要メソッド体が selfhost builder で MIR 関数化できる。 - `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 後処理 ### Step 5 — call resolve / methodize 後処理
- 目的: 生成済み MIR に対して call resolver / methodize pass をかけ、Rust provider と同じ命名・呼び出し形式を実現。 - 目的: 生成済み MIR に対して call resolver / methodize pass をかけ、Rust provider と同じ命名・呼び出し形式を実現。
@ -211,4 +469,71 @@ Status: planning-only実装着手前の設計・分析フェーズ
- `tools/selfhost/build_stage1.sh` を selfhost-first で回し、selfhost builder 由来の MIR で `emit program-json` / `emit mir-json` / `build exe` が通ることを確認。 - `tools/selfhost/build_stage1.sh` を selfhost-first で回し、selfhost builder 由来の MIR で `emit program-json` / `emit mir-json` / `build exe` が通ることを確認。
- 問題が無ければ `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻す別PRでも可 - 問題が無ければ `HAKO_SELFHOST_BUILDER_FIRST=1` を既定に戻す別PRでも可
作業順は Step 0 → 1 → 2 → 3 → 4 → 5 → 6 を想定、各ステップで必ず docsこのファイルCURRENT_TASKとスモーク/テストを更新する。*** 作業順は 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 プロファイルへの昇格を検討する。
***

View File

@ -16,6 +16,7 @@ using "hako.mir.builder.internal.lower_if_compare_varint" as LowerIfCompareVarIn
using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox using "hako.mir.builder.internal.lower_if_compare_varvar" as LowerIfCompareVarVarBox
using "hako.mir.builder.internal.lower_loop_simple_box" as LowerLoopSimpleBox using "hako.mir.builder.internal.lower_loop_simple_box" as LowerLoopSimpleBox
using "hako.mir.builder.internal.lower_loop_sum_bc_box" as LowerLoopSumBcBox using "hako.mir.builder.internal.lower_loop_sum_bc_box" as LowerLoopSumBcBox
using "hako.mir.builder.internal.lower_loop_multi_carrier_box" as LowerLoopMultiCarrierBox
static box FuncBodyBasicLowerBox { static box FuncBodyBasicLowerBox {
lower(func_name, box_name, params_arr, body_json) { lower(func_name, box_name, params_arr, body_json) {
@ -34,7 +35,13 @@ static box FuncBodyBasicLowerBox {
return null return null
} }
// 2) Non-Loop processing: Local/If/Return patterns only // 2) Try simple Return(Method ...) patternparams ベースの receiver のみ)
{
local mret = me._try_lower_return_method(func_name, box_name, params_arr, s)
if mret != null { return mret }
}
// 3) Non-Loop processing: Local/If/Return パターンのみ
local lowered = me._try_lower_local_if_return(func_name, box_name, params_arr, s) local lowered = me._try_lower_local_if_return(func_name, box_name, params_arr, s)
if lowered != null { return lowered } if lowered != null { return lowered }
@ -353,19 +360,203 @@ static box FuncBodyBasicLowerBox {
return "{\"functions\":[{\"name\":\"" + target + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[" + blocks + "]}]}" return "{\"functions\":[{\"name\":\"" + target + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[" + blocks + "]}]}"
} }
// Simple Return(Method recv.method(args)) lowering for param-based receivers
// Scope: ArrayBox.size/get, StringBox.length のような単純パターンのみ
method _try_lower_return_method(func_name, box_name, params_arr, body_json) {
if body_json == null { return null }
local s = "" + body_json
// Expect Return(Method(...)) at top level
local ret_idx = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", 0)
if ret_idx < 0 { return null }
local m_idx = JsonFragBox.index_of_from(s, "\"type\":\"Method\"", ret_idx)
if m_idx < 0 { return null }
// method name
local name_idx = JsonFragBox.index_of_from(s, "\"method\":", m_idx)
if name_idx < 0 { return null }
local mname = JsonFragBox.read_string_after(s, name_idx + 9)
if mname == null { return null }
// receiver: Var only
local recv_idx = JsonFragBox.index_of_from(s, "\"recv\":{", m_idx)
if recv_idx < 0 { return null }
local rvar_idx = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", recv_idx)
if rvar_idx < 0 { return null }
local rname_idx = JsonFragBox.index_of_from(s, "\"name\":", rvar_idx)
if rname_idx < 0 { return null }
local recv_name = JsonFragBox.read_string_after(s, rname_idx + 7)
if recv_name == null { return null }
// Restrict to params-based receiver onlyローカル受信は未対応
local is_param_recv = 0
{
local i = 0
local n = params_arr.length()
loop(i < n) {
if ("" + params_arr.get(i)) == recv_name { is_param_recv = 1 break }
i = i + 1
}
}
if is_param_recv == 0 { return null }
// args arrayInt or Var のみ許可)
local arg_regs = new ArrayBox()
local args_idx = JsonFragBox.index_of_from(s, "\"args\":[", m_idx)
if args_idx >= 0 {
local scan = args_idx + 8
loop(scan < s.length()) {
local t_idx = JsonFragBox.index_of_from(s, "\"type\":\"", scan)
if t_idx < 0 { break }
if t_idx > m_idx + 256 { break }
local atype = JsonFragBox.read_string_after(s, t_idx + 7)
if atype == null { break }
if atype == "Int" {
local v_idx = JsonFragBox.index_of_from(s, "\"value\":", t_idx)
if v_idx < 0 { break }
local v = JsonFragBox.read_int_after(s, v_idx + 8)
if v == null { break }
local info = new MapBox()
info.set("kind", "Int")
info.set("value", JsonFragBox._str_to_int("" + v))
arg_regs.push(info)
} else if atype == "Var" {
local n_idx = JsonFragBox.index_of_from(s, "\"name\":", t_idx)
if n_idx < 0 { break }
local aname = JsonFragBox.read_string_after(s, n_idx + 7)
if aname == null { break }
local info = new MapBox()
info.set("kind", "Var")
info.set("name", aname)
arg_regs.push(info)
}
scan = t_idx + 20
}
}
// Map params to registers
local param_map = new MapBox()
local next_reg = 1
{
local pi = 0
local pn = params_arr.length()
loop(pi < pn) {
param_map.set("" + params_arr.get(pi), "" + next_reg)
next_reg = next_reg + 1
pi = pi + 1
}
}
// receiver register
local recv_reg = param_map.get(recv_name)
if recv_reg == null { return null }
local recv_reg_i = JsonFragBox._str_to_int("" + recv_reg)
// Build arg reg list
local arg_ids = new ArrayBox()
{
local i = 0
local n = arg_regs.length()
loop(i < n) {
local info = arg_regs.get(i)
local kind = "" + info.get("kind")
if kind == "Int" {
local v = info.get("value")
local reg = next_reg
next_reg = next_reg + 1
info.set("reg", reg)
} else if kind == "Var" {
local aname = "" + info.get("name")
local preg = param_map.get(aname)
if preg == null { return null }
info.set("reg", JsonFragBox._str_to_int("" + preg))
}
arg_ids.push(info.get("reg"))
i = i + 1
}
}
// Build instructions: const for Int args, then mir_call + ret
local insts = ""
{
local i = 0
local n = arg_regs.length()
loop(i < n) {
local info = arg_regs.get(i)
local kind = "" + info.get("kind")
if kind == "Int" {
local reg = info.get("reg")
local v = info.get("value")
if insts != "" { insts = insts + "," }
insts = insts + "{\\\"op\\\":\\\"const\\\",\\\"dst\\\":" + reg + ",\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + v + "}}"
}
i = i + 1
}
}
// Build args JSON list引数のみ。receiver は callee.receiver に載せる)
local args_json = ""
{
local i = 0
local n = arg_ids.length()
loop(i < n) {
if args_json != "" { args_json = args_json + "," }
args_json = args_json + ("" + arg_ids.get(i))
i = i + 1
}
}
local result_reg = next_reg
next_reg = next_reg + 1
// Box type 判定(現段階では ArrayBox.size/get と StringBox.length のみ)
local box_type = null
local kind = ""
if mname == "size" || mname == "get" {
box_type = "ArrayBox"
kind = "array"
} else if mname == "length" {
// String.length()len/size エイリアスは別経路で扱う)
if arg_ids.length() != 0 { return null }
box_type = "StringBox"
kind = "string"
} else {
return null
}
if insts != "" { insts = insts + "," }
insts = insts + "{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":" + result_reg + ",\\\"mir_call\\\":{\\\"callee\\\":{\\\"type\\\":\\\"Method\\\",\\\"box_name\\\":\\\"" + box_type + "\\\",\\\"method\\\":\\\"" + mname + "\\\",\\\"receiver\\\":" + recv_reg_i + "},\\\"args\\\":[\" + args_json + \"],\\\"effects\\\":[]}}"
insts = insts + ",{\\\"op\\\":\\\"ret\\\",\\\"value\\\":" + result_reg + "}"
local params_json = me._build_params_json(params_arr)
local target = me._build_func_name(box_name, func_name, params_arr)
if target == null { return null }
if env.get("HAKO_SELFHOST_TRACE") == "1" {
local tag = "[funcs/basic:method." + kind + "] "
print(tag + target + " method=" + mname)
}
return "{\"functions\":[{\"name\":\"" + target + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[{\"id\":0,\"instructions\":[" + insts + "]}]}]}"
}
method _try_lower_loop(func_name, box_name, params_arr, body_json) { method _try_lower_loop(func_name, box_name, params_arr, body_json) {
// 1) LoopForm の Loop を持つ Program(JSON) であることを軽く確認 // 1) LoopForm の Loop を持つ Program(JSON) であることを軽く確認
if !(body_json.contains("\"type\":\"Loop\"")) { return null } if !(body_json.contains("\"type\":\"Loop\"")) { return null }
// 2) まず sum_bc 用の lower を試す // 2) まず sum_bc 用の lower を試すbreak/continue付き
{ local out = LowerLoopSumBcBox.try_lower(body_json) { local out = LowerLoopSumBcBox.try_lower(body_json)
if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:loop.sum_bc]") } } if out != null { return me._rebind(out, func_name, box_name, params_arr, "[funcs/basic:loop.sum_bc]") } }
// 3) 次に simple loop 用 lower を試す // 3) 次に multi_carrier 用 lower を試すfibonacci風
{ local out_mc = LowerLoopMultiCarrierBox.try_lower(body_json)
if out_mc != null { return me._rebind(out_mc, func_name, box_name, params_arr, "[funcs/basic:loop.multi_carrier]") } }
// 4) 最後に simple loop 用 lower を試す(単純カウンタ)
{ local out2 = LowerLoopSimpleBox.try_lower(body_json) { local out2 = LowerLoopSimpleBox.try_lower(body_json)
if out2 != null { return me._rebind(out2, func_name, box_name, params_arr, "[funcs/basic:loop.simple]") } } if out2 != null { return me._rebind(out2, func_name, box_name, params_arr, "[funcs/basic:loop.simple]") } }
// 4) ここまででダメなら selfhost 側では未対応 // 5) ここまででダメなら selfhost 側では未対応
return null return null
} }
} }

View File

@ -0,0 +1,43 @@
// cli_entry_box.hako — CliEntryLowerBox
// Responsibility:
// Detect Stage1 CLI entry pattern (Main.main → HakoCli.run) in Program(JSON v0).
// Scope:
// - Observability only構造検出用。MIR は生成せず、null を返す。
// - ring1 レイヤStage1 CLIの入口構造を把握するための補助。
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box CliEntryLowerBox {
// Scan Program(JSON v0) for a Main.main → HakoCli.run entry pattern.
// Returns null always; emits a tag on detection when HAKO_SELFHOST_TRACE=1.
method scan(program_json) {
if program_json == null { return null }
local s = "" + program_json
// Quick guard: must be Program(JSON v0)
if s.indexOf("\"kind\":\"Program\"") < 0 { return null }
// Heuristic 1: Program.body 内に HakoCli の New があるか
local new_cli_idx = JsonFragBox.index_of_from(s, "\"class\":\"HakoCli\"", 0)
if new_cli_idx < 0 { return null }
// Heuristic 2: defs 配列に HakoCli.run が含まれているか
local box_idx = JsonFragBox.index_of_from(s, "\"box\":\"HakoCli\"", 0)
if box_idx < 0 { return null }
local name_idx = JsonFragBox.index_of_from(s, "\"name\":\"run\"", box_idx)
if name_idx < 0 { return null }
// Optional: method 呼び出しパターンMain.main 本体)を軽く確認
// - Return(Method recv=Var(\"cli\"), method=\"run\", args=[Var(\"args\")]) を期待。
local run_call_idx = JsonFragBox.index_of_from(s, "\"method\":\"run\"", new_cli_idx)
if run_call_idx < 0 { return null }
// All heuristics satisfied → tag 出力のみ(観測用)
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/cli:entry_detected] main=Main.main run=HakoCli.run/2")
}
return null
}
}

View File

@ -0,0 +1,273 @@
// cli_run_lower_box.hako — CliRunLowerBox
// Responsibility:
// - Specialized lowering hook for HakoCli.run defs (Stage1 CLI entry).
// Scope:
// - Current implementation is observational only: it validates that the
// target is HakoCli.run and, when tracing is enabled, emits a tag.
// - Returns null so that generic lowerers / Rust provider still handle
// the actual MIR generation.
// - Future work (Phase 25.1b Step2+): lower simple HakoCli.run bodies
// (run/build/emit/check 分岐) to MIR(JSON) when the shape is supported.
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box CliRunLowerBox {
// Attempt to lower HakoCli.run body to MIR(JSON).
// Step 2.x: shape が既知のパターンと一致する場合に限り、MIR(JSON) 生成を行う。
// 既定では環境変数 HAKO_MIR_BUILDER_CLI_RUN=1 のときのみ自前の MIR を返し、
// それ以外は provider/既存 lower にフォールバックする。
method lower_run(func_name, box_name, params_arr, body_json, func_map) {
if box_name == null || func_name == null { return null }
if box_name != "HakoCli" { return null }
if func_name != "run" { return null }
// Step 2: only check JSON 形状MVP パターン)を検証する。
// 形が想定と違えば Fail-Fast で provider に退避する。
local ok = me._check_shape(body_json)
if ok != 1 {
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/cli:run_lower:unsupported] reason=shape-mismatch")
}
return null
}
// 既定では MIR 生成はトグルで有効化freeze ポリシーに従い既定OFF
local flag = env.get("HAKO_MIR_BUILDER_CLI_RUN")
if flag == null { flag = "0" }
if flag != "1" {
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/cli:run_lower:shape-ok] box=" + box_name + " func=" + func_name + " (MIR disabled)")
}
return null
}
// ここまで来たら、HakoCli.run を selfhost builder で MIR に降ろす。
local mir = me._emit_mir(box_name, func_name, params_arr)
if mir == null || mir == "" {
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/cli:run_lower:emit-failed]")
}
return null
}
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[builder/cli:run_lower:ok] " + box_name + "." + func_name + "/2")
}
return mir
}
// Check that body_json roughly matches the expected StageB shape
// for HakoCli.run (argc/init, args.size, cmd_raw/cmd, cmd_* branches, return 2).
method _check_shape(body_json) {
if body_json == null { return 0 }
local s = "" + body_json
// 1) Local argc = Int(0)
local loc_idx = JsonFragBox.index_of_from(s, "\"type\":\"Local\"", 0)
if loc_idx < 0 { return 0 }
local argc_idx = JsonFragBox.index_of_from(s, "\"name\":\"argc\"", loc_idx)
if argc_idx < 0 { return 0 }
local int_idx = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", argc_idx)
if int_idx < 0 { return 0 }
local val_idx = JsonFragBox.index_of_from(s, "\"value\":", int_idx)
if val_idx < 0 { return 0 }
local v0 = JsonFragBox.read_int_after(s, val_idx + 8)
if v0 == null { return 0 }
if JsonFragBox._str_to_int("" + v0) != 0 { return 0 }
// 2) args.size() が存在するかIf 内で argc に代入されていることを緩く確認)
local size_idx = JsonFragBox.index_of_from(s, "\"method\":\"size\"", argc_idx)
if size_idx < 0 { return 0 }
local recv_idx = JsonFragBox.index_of_from(s, "\"recv\":{", size_idx)
if recv_idx < 0 { return 0 }
local args_var_idx = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", recv_idx)
if args_var_idx < 0 { return 0 }
local args_name_idx = JsonFragBox.index_of_from(s, "\"name\":\"args\"", args_var_idx)
if args_name_idx < 0 { return 0 }
// 3) argc == 0 then return 1Compare と Return Int(1) を緩く確認)
local cmp_idx = JsonFragBox.index_of_from(s, "\"type\":\"Compare\"", size_idx)
if cmp_idx < 0 { return 0 }
local lhs_idx = JsonFragBox.index_of_from(s, "\"lhs\":{", cmp_idx)
if lhs_idx < 0 { return 0 }
local lhs_name_idx = JsonFragBox.index_of_from(s, "\"name\":\"argc\"", lhs_idx)
if lhs_name_idx < 0 { return 0 }
local rhs_idx = JsonFragBox.index_of_from(s, "\"rhs\":{", cmp_idx)
if rhs_idx < 0 { return 0 }
local rhs_int_idx = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", rhs_idx)
if rhs_int_idx < 0 { return 0 }
local rhs_val_idx = JsonFragBox.index_of_from(s, "\"value\":", rhs_int_idx)
if rhs_val_idx < 0 { return 0 }
local v1 = JsonFragBox.read_int_after(s, rhs_val_idx + 8)
if v1 == null { return 0 }
if JsonFragBox._str_to_int("" + v1) != 0 { return 0 }
// Return Int(1) somewhere after this compare
local ret1_idx = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", cmp_idx)
if ret1_idx < 0 { return 0 }
local ret1_int_idx = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", ret1_idx)
if ret1_int_idx < 0 { return 0 }
local ret1_val_idx = JsonFragBox.index_of_from(s, "\"value\":", ret1_int_idx)
if ret1_val_idx < 0 { return 0 }
local rv1 = JsonFragBox.read_int_after(s, ret1_val_idx + 8)
if rv1 == null { return 0 }
if JsonFragBox._str_to_int("" + rv1) != 1 { return 0 }
// 4) Local cmd_raw = args.get(0)
local cmd_raw_idx = JsonFragBox.index_of_from(s, "\"name\":\"cmd_raw\"", ret1_idx)
if cmd_raw_idx < 0 { return 0 }
local get_idx = JsonFragBox.index_of_from(s, "\"method\":\"get\"", cmd_raw_idx)
if get_idx < 0 { return 0 }
local get_recv_idx = JsonFragBox.index_of_from(s, "\"recv\":{", get_idx)
if get_recv_idx < 0 { return 0 }
local get_args_var_idx = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", get_recv_idx)
if get_args_var_idx < 0 { return 0 }
local get_args_name_idx = JsonFragBox.index_of_from(s, "\"name\":\"args\"", get_args_var_idx)
if get_args_name_idx < 0 { return 0 }
// 5) Local cmd = "" + cmd_raw Binary + を緩く確認)
local cmd_idx = JsonFragBox.index_of_from(s, "\"name\":\"cmd\"", cmd_raw_idx)
if cmd_idx < 0 { return 0 }
// 単純に "cmd_raw" が cmd 定義以降にも現れることを確認
local cmdraw_ref_idx = JsonFragBox.index_of_from(s, "\"name\":\"cmd_raw\"", cmd_idx)
if cmdraw_ref_idx < 0 { return 0 }
// 6) run/build/emit/check 文字列と cmd_* 呼び出しが存在するか(順序は緩め)
if s.indexOf("\"run\"") < 0 { return 0 }
if s.indexOf("\"build\"") < 0 { return 0 }
if s.indexOf("\"emit\"") < 0 { return 0 }
if s.indexOf("\"check\"") < 0 { return 0 }
if s.indexOf("\"cmd_run\"") < 0 && s.indexOf("\"cmd_run\"") < 0 { return 0 }
if s.indexOf("\"cmd_build\"") < 0 { return 0 }
if s.indexOf("\"cmd_emit\"") < 0 { return 0 }
if s.indexOf("\"cmd_check\"") < 0 { return 0 }
// 7) 最後の Return Int(2)unknown command
// - body の末尾付近から Return を探して Int(2) を期待する。
local last_ret_idx = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", cmd_idx)
local search_pos = last_ret_idx
loop(true) {
local next_idx = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", search_pos + 1)
if next_idx < 0 { break }
last_ret_idx = next_idx
search_pos = next_idx
}
if last_ret_idx < 0 { return 0 }
local last_int_idx = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", last_ret_idx)
if last_int_idx < 0 { return 0 }
local last_val_idx = JsonFragBox.index_of_from(s, "\"value\":", last_int_idx)
if last_val_idx < 0 { return 0 }
local rv2 = JsonFragBox.read_int_after(s, last_val_idx + 8)
if rv2 == null { return 0 }
if JsonFragBox._str_to_int("" + rv2) != 2 { return 0 }
return 1
}
// Emit minimal MIR(JSON) for HakoCli.run/2.
// 簡略化されたブロック構造:
// - r1 = me, r2 = args
// - argc = args.size()
// - argc == 0 → ret 1
// - cmd_raw = args.get(0)
// - cmd_raw を各リテラル "run"/"build"/"emit"/"check" と比較し、me.cmd_* を呼び出す
// - どれでもなければ ret 2
method _emit_mir(box_name, func_name, params_arr) {
// params_json は単純に [1,2] とし、locals は空配列とする。
local params_json = "[1,2]"
// レジスタ割り当て:
// r1=me, r2=args, r3=argc, r4=tmp0, r5=cmp0,
// r6=ret_tmp, r7=index0, r8=cmd_raw,
// r9..r? = 各種文字列リテラルと比較結果。
// block 0: argc=0; argc = args.size(); cmp argc==0 → branch
local b0 = ""
b0 = b0 + "{\"op\":\"const\",\"dst\":3,\"value\":{\"type\":\"i64\",\"value\":0}}"
b0 = b0 + ",{\"op\":\"boxcall\",\"box\":2,\"method\":\"size\",\"args\":[],\"dst\":3}"
b0 = b0 + ",{\"op\":\"const\",\"dst\":4,\"value\":{\"type\":\"i64\",\"value\":0}}"
b0 = b0 + ",{\"op\":\"compare\",\"operation\":\"==\",\"lhs\":3,\"rhs\":4,\"dst\":5}"
b0 = b0 + ",{\"op\":\"branch\",\"cond\":5,\"then\":1,\"else\":2}"
// block 1: argc==0 → ret 1
local b1 = ""
b1 = b1 + "{\"op\":\"const\",\"dst\":6,\"value\":{\"type\":\"i64\",\"value\":1}}"
b1 = b1 + ",{\"op\":\"ret\",\"value\":6}"
// block 2: argc>0 → cmd_raw = args.get(0); jump to cmd dispatch
local b2 = ""
b2 = b2 + "{\"op\":\"const\",\"dst\":7,\"value\":{\"type\":\"i64\",\"value\":0}}"
b2 = b2 + ",{\"op\":\"boxcall\",\"box\":2,\"method\":\"get\",\"args\":[7],\"dst\":8}"
b2 = b2 + ",{\"op\":\"jump\",\"target\":3}"
// block 3: cmd == \"run\" ?
local b3 = ""
b3 = b3 + "{\"op\":\"const\",\"dst\":9,"
b3 = b3 + "\"value\":{\"type\":{\"box_type\":\"StringBox\",\"kind\":\"handle\"},\"value\":\"run\"}}"
b3 = b3 + ",{\"op\":\"compare\",\"operation\":\"==\",\"lhs\":8,\"rhs\":9,\"dst\":10}"
b3 = b3 + ",{\"op\":\"branch\",\"cond\":10,\"then\":4,\"else\":5}"
// block 4: run → me.cmd_run(args); ret
local b4 = ""
b4 = b4 + "{\"op\":\"boxcall\",\"box\":1,\"method\":\"cmd_run\",\"args\":[2],\"dst\":11}"
b4 = b4 + ",{\"op\":\"ret\",\"value\":11}"
// block 5: cmd == \"build\" ?
local b5 = ""
b5 = b5 + "{\"op\":\"const\",\"dst\":12,"
b5 = b5 + "\"value\":{\"type\":{\"box_type\":\"StringBox\",\"kind\":\"handle\"},\"value\":\"build\"}}"
b5 = b5 + ",{\"op\":\"compare\",\"operation\":\"==\",\"lhs\":8,\"rhs\":12,\"dst\":13}"
b5 = b5 + ",{\"op\":\"branch\",\"cond\":13,\"then\":6,\"else\":7}"
// block 6: build → me.cmd_build(args); ret
local b6 = ""
b6 = b6 + "{\"op\":\"boxcall\",\"box\":1,\"method\":\"cmd_build\",\"args\":[2],\"dst\":14}"
b6 = b6 + ",{\"op\":\"ret\",\"value\":14}"
// block 7: cmd == \"emit\" ?
local b7 = ""
b7 = b7 + "{\"op\":\"const\",\"dst\":15,"
b7 = b7 + "\"value\":{\"type\":{\"box_type\":\"StringBox\",\"kind\":\"handle\"},\"value\":\"emit\"}}"
b7 = b7 + ",{\"op\":\"compare\",\"operation\":\"==\",\"lhs\":8,\"rhs\":15,\"dst\":16}"
b7 = b7 + ",{\"op\":\"branch\",\"cond\":16,\"then\":8,\"else\":9}"
// block 8: emit → me.cmd_emit(args); ret
local b8 = ""
b8 = b8 + "{\"op\":\"boxcall\",\"box\":1,\"method\":\"cmd_emit\",\"args\":[2],\"dst\":17}"
b8 = b8 + ",{\"op\":\"ret\",\"value\":17}"
// block 9: cmd == \"check\" ?
local b9 = ""
b9 = b9 + "{\"op\":\"const\",\"dst\":18,"
b9 = b9 + "\"value\":{\"type\":{\"box_type\":\"StringBox\",\"kind\":\"handle\"},\"value\":\"check\"}}"
b9 = b9 + ",{\"op\":\"compare\",\"operation\":\"==\",\"lhs\":8,\"rhs\":18,\"dst\":19}"
b9 = b9 + ",{\"op\":\"branch\",\"cond\":19,\"then\":10,\"else\":11}"
// block 10: check → me.cmd_check(args); ret
local b10 = ""
b10 = b10 + "{\"op\":\"boxcall\",\"box\":1,\"method\":\"cmd_check\",\"args\":[2],\"dst\":20}"
b10 = b10 + ",{\"op\":\"ret\",\"value\":20}"
// block 11: unknown → ret 2
local b11 = ""
b11 = b11 + "{\"op\":\"const\",\"dst\":21,\"value\":{\"type\":\"i64\",\"value\":2}}"
b11 = b11 + ",{\"op\":\"ret\",\"value\":21}"
local blocks = ""
blocks = blocks + "{\"id\":0,\"instructions\":[" + b0 + "]}"
blocks = blocks + ",{\"id\":1,\"instructions\":[" + b1 + "]}"
blocks = blocks + ",{\"id\":2,\"instructions\":[" + b2 + "]}"
blocks = blocks + ",{\"id\":3,\"instructions\":[" + b3 + "]}"
blocks = blocks + ",{\"id\":4,\"instructions\":[" + b4 + "]}"
blocks = blocks + ",{\"id\":5,\"instructions\":[" + b5 + "]}"
blocks = blocks + ",{\"id\":6,\"instructions\":[" + b6 + "]}"
blocks = blocks + ",{\"id\":7,\"instructions\":[" + b7 + "]}"
blocks = blocks + ",{\"id\":8,\"instructions\":[" + b8 + "]}"
blocks = blocks + ",{\"id\":9,\"instructions\":[" + b9 + "]}"
blocks = blocks + ",{\"id\":10,\"instructions\":[" + b10 + "]}"
blocks = blocks + ",{\"id\":11,\"instructions\":[" + b11 + "]}"
local full_name = "" + box_name + "." + func_name + "/2"
local mir = "{\"name\":\"" + full_name + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[" + blocks + "]}"
return mir
}
}

View File

@ -0,0 +1,49 @@
// cli_run_shape_box.hako — CliRunShapeScannerBox
// Responsibility:
// - Inspect Program(JSON v0) for HakoCli.run defs and collect a coarse
// view of its branch structure (CLI subcommands).
// Scope:
// - Observability only. Returns MapBox with metadata or null.
// - Used as a structural input / trace source for CliRunLowerBox.
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box CliRunShapeScannerBox {
// Scan Program(JSON v0) and return a MapBox with:
// - "has_run" : 1 if HakoCli.run is present, else 0/null
// - "branches": ArrayBox of subcommand names (heuristic)
// Returns null if HakoCli.run is not present.
method scan(program_json) {
if program_json == null { return null }
local s = "" + program_json
// Must be Program(JSON v0)
if s.indexOf("\"kind\":\"Program\"") < 0 { return null }
// Look for defs entry: {"box":"HakoCli","name":"run", ...}
local box_idx = JsonFragBox.index_of_from(s, "\"box\":\"HakoCli\"", 0)
if box_idx < 0 { return null }
local name_idx = JsonFragBox.index_of_from(s, "\"name\":\"run\"", box_idx)
if name_idx < 0 { return null }
local info = new MapBox()
info.set("has_run", 1)
// Heuristic branch detection based on string literals used in run body.
// This is intentionally coarse: it is only used for trace / planning.
local branches = new ArrayBox()
if s.indexOf("\"run\"") >= 0 { branches.push("run") }
if s.indexOf("\"build\"") >= 0 { branches.push("build") }
if s.indexOf("\"emit\"") >= 0 { branches.push("emit") }
if s.indexOf("\"check\"") >= 0 { branches.push("check") }
info.set("branches", branches)
if env.get("HAKO_SELFHOST_TRACE") == "1" {
local n = branches.length()
print("[builder/cli:run_shape] has_run=1 branches=" + ("" + n))
}
return info
}
}

View File

@ -0,0 +1,141 @@
// extern_call_box.hako — ExternCallLowerBox
// Responsibility:
// Lower simple hostbridge.extern_invoke-based patterns in defs to MIR v1 externcall.
// Scope (MVP):
// Return(hostbridge.extern_invoke("env.codegen","emit_object", Var(arg)))
// Return(hostbridge.extern_invoke("env.codegen","link_object", Var(arg)))
// Notes:
// - Only params-based argument (Var that refers to a function parameter) is supported.
// - More complex shapes (locals/arrays/maps) are left to Rust provider / other lowers.
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box ExternCallLowerBox {
method lower_hostbridge(func_name, box_name, params_arr, body_json) {
if body_json == null { return null }
local s = "" + body_json
// Expect Return(Method(...)) at top level
local ret_idx = JsonFragBox.index_of_from(s, "\"type\":\"Return\"", 0)
if ret_idx < 0 { return null }
local m_idx = JsonFragBox.index_of_from(s, "\"type\":\"Method\"", ret_idx)
if m_idx < 0 { return null }
// method name should be extern_invoke
local name_idx = JsonFragBox.index_of_from(s, "\"method\":", m_idx)
if name_idx < 0 { return null }
local mname = JsonFragBox.read_string_after(s, name_idx + 9)
if mname == null || mname != "extern_invoke" { return null }
// receiver must be Var("hostbridge")
local recv_idx = JsonFragBox.index_of_from(s, "\"recv\":{", m_idx)
if recv_idx < 0 { return null }
local rvar_idx = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", recv_idx)
if rvar_idx < 0 { return null }
local rname_idx = JsonFragBox.index_of_from(s, "\"name\":", rvar_idx)
if rname_idx < 0 { return null }
local recv_name = JsonFragBox.read_string_after(s, rname_idx + 7)
if recv_name == null || recv_name != "hostbridge" { return null }
// args[0]: "env.codegen", args[1]: "emit_object" | "link_object", args[2]: Var(arg)
local args_idx = JsonFragBox.index_of_from(s, "\"args\":[", m_idx)
if args_idx < 0 { return null }
// iface name (first Str.value)
local iface = null
{
local t0 = JsonFragBox.index_of_from(s, "\"type\":\"Str\"", args_idx)
if t0 < 0 { return null }
local v0 = JsonFragBox.index_of_from(s, "\"value\":\"", t0)
if v0 < 0 { return null }
iface = JsonFragBox.read_string_after(s, v0 + 8)
if iface == null { return null }
}
if iface != "env.codegen" { return null }
// method name string (second Str.value)
local method = null
{
local t1 = JsonFragBox.index_of_from(s, "\"type\":\"Str\"", args_idx + 1)
if t1 < 0 { return null }
local v1 = JsonFragBox.index_of_from(s, "\"value\":\"", t1)
if v1 < 0 { return null }
method = JsonFragBox.read_string_after(s, v1 + 8)
if method == null { return null }
}
if !(method == "emit_object" || method == "link_object") { return null }
// arg Var name (third arg)
local arg_name = null
{
local t2 = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", args_idx)
// skip any preceding Str nodes
if t2 < 0 { return null }
// Move to Var after the two Str nodes
local t2b = JsonFragBox.index_of_from(s, "\"type\":\"Var\"", t2 + 1)
if t2b >= 0 { t2 = t2b }
local n2 = JsonFragBox.index_of_from(s, "\"name\":", t2)
if n2 < 0 { return null }
arg_name = JsonFragBox.read_string_after(s, n2 + 7)
if arg_name == null { return null }
}
// Map params to registers
local param_map = new MapBox()
local next_reg = 1
{
local pi = 0
local pn = params_arr.length()
loop(pi < pn) {
param_map.set("" + params_arr.get(pi), "" + next_reg)
next_reg = next_reg + 1
pi = pi + 1
}
}
local arg_reg = param_map.get(arg_name)
if arg_reg == null { return null }
local arg_reg_i = JsonFragBox._str_to_int("" + arg_reg)
local result_reg = next_reg
next_reg = next_reg + 1
// Build externcall + ret
local func_full = "env.codegen." + method
local insts = "{\\\"op\\\":\\\"externcall\\\",\\\"func\\\":\\\"" + func_full + "\\\",\\\"args\\\":[" + arg_reg_i + "],\\\"dst\\\":" + result_reg + "}"
insts = insts + ",{\\\"op\\\":\\\"ret\\\",\\\"value\\\":" + result_reg + "}"
// Function header
local params_json = me._build_params_json(params_arr)
local target = me._build_func_name(box_name, func_name, params_arr)
if target == null { return null }
if env.get("HAKO_SELFHOST_TRACE") == "1" {
print("[funcs/basic:extern.codegen] " + target + " func=" + func_full)
}
return "{\"functions\":[{\"name\":\"" + target + "\",\"params\":" + params_json + ",\"locals\":[],\"blocks\":[{\"id\":0,\"instructions\":[" + insts + "]}]}]}"
}
method _build_func_name(box_name, func_name, params_arr) {
if box_name == null || func_name == null { return null }
local arity = 0
if params_arr != null { arity = JsonFragBox._str_to_int("" + params_arr.length()) }
return ("" + box_name) + "." + ("" + func_name) + "/" + arity
}
method _build_params_json(params_arr) {
if params_arr == null { return "[]" }
local params_json = "["
local i = 0
local n = params_arr.length()
loop(i < n) {
if i > 0 { params_json = params_json + "," }
params_json = params_json + "\\\"" + ("" + params_arr.get(i)) + "\\\""
i = i + 1
}
params_json = params_json + "]"
return params_json
}
}

View File

@ -6,6 +6,10 @@
using selfhost.shared.json.utils.json_frag as JsonFragBox using selfhost.shared.json.utils.json_frag as JsonFragBox
using lang.mir.builder.func_body.basic_lower_box as FuncBodyBasicLowerBox using lang.mir.builder.func_body.basic_lower_box as FuncBodyBasicLowerBox
using lang.mir.builder.func_body.extern_call_box as ExternCallLowerBox
using lang.mir.builder.func_body.cli_entry_box as CliEntryLowerBox
using lang.mir.builder.func_body.cli_run_shape_box as CliRunShapeScannerBox
using lang.mir.builder.func_body.cli_run_lower_box as CliRunLowerBox
static box FuncLoweringBox { static box FuncLoweringBox {
// Lower function definitions to MIR // Lower function definitions to MIR
@ -21,6 +25,13 @@ static box FuncLoweringBox {
if trace_env != null && ("" + trace_env) == "1" { trace_funcs = 1 } if trace_env != null && ("" + trace_env) == "1" { trace_funcs = 1 }
} }
// Optional: detect Stage1 CLI entry (Main.main → HakoCli.run) for observability.
// Observes ring1 構造のみで、MIR 生成や ring0 への影響は与えない。
CliEntryLowerBox.scan(s)
// Optional: scan HakoCli.run の分岐構造run/build/emit/check 等)も観測する。
// 戻り値は現状未使用だが、タグとメタ情報で形状を把握しておく。
CliRunShapeScannerBox.scan(s)
// Check for "defs" key in Program JSON // Check for "defs" key in Program JSON
local defs_idx = JsonFragBox.index_of_from(s, "\"defs\":", 0) local defs_idx = JsonFragBox.index_of_from(s, "\"defs\":", 0)
if defs_idx < 0 { return "" } if defs_idx < 0 { return "" }
@ -316,11 +327,29 @@ static box FuncLoweringBox {
method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) { method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) {
local body_str = "" + body_json local body_str = "" + body_json
// Specialized path (stub) for Stage1 CLI run entry.
// 現時点では観測のみで、MIR は生成せず null を返す。
{
local is_cli = 0
if box_name != null && func_name != null {
if box_name == "HakoCli" && func_name == "run" { is_cli = 1 }
}
if is_cli == 1 {
local cli_mir = CliRunLowerBox.lower_run(func_name, box_name, params_arr, body_str, func_map)
if cli_mir != null { return cli_mir }
}
}
{ {
local basic = FuncBodyBasicLowerBox.lower(func_name, box_name, params_arr, body_str) local basic = FuncBodyBasicLowerBox.lower(func_name, box_name, params_arr, body_str)
if basic != null { return basic } if basic != null { return basic }
} }
{
local ext = ExternCallLowerBox.lower_hostbridge(func_name, box_name, params_arr, body_str)
if ext != null { return ext }
}
// Check for Return statement // Check for Return statement
local ret_idx = JsonFragBox.index_of_from(body_str, "\"type\":\"Return\"", 0) local ret_idx = JsonFragBox.index_of_from(body_str, "\"type\":\"Return\"", 0)
if ret_idx < 0 { return null } if ret_idx < 0 { return null }

View File

@ -0,0 +1,99 @@
// lower_loop_multi_carrier_box.hako — Multi-carrier loop (fibonacci-style) lowering
// Responsibility:
// - Detect and lower "multi-carrier" loops (e.g. fib: a,b,t carried variables)
// - Extract loop metadata (var name, limit, carrier count) from Program(JSON v0)
// - Delegate to LoopFormBox.build2 with mode="multi_count"
// Scope (Phase 25.1b):
// - Recognize fibonacci-style loops with 2+ carrier variables
// - Fail-Fast if pattern doesn't match multi-carrier requirements
// - Fall back to Rust provider for unsupported patterns
using selfhost.shared.json.utils.json_frag as JsonFragBox
using "hako.mir.builder.internal.loop_scan" as LoopScanBox
using "hako.mir.builder.internal.loop_opts_adapter" as LoopOptsBox
using "hako.mir.builder.internal.builder_config" as BuilderConfigBox
static box LowerLoopMultiCarrierBox {
// Try to recognize and lower a multi-carrier loop in Program(JSON v0).
// Pattern: loop with multiple Local/Assign indicating carried state (fibonacci-style)
method try_lower(program_json) {
if program_json == null { return null }
local s = "" + program_json
// 1) Find Loop with cond Compare
local k_loop = JsonFragBox.index_of_from(s, "\"type\":\"Loop\"", 0)
if k_loop < 0 { return null }
local k_cmp = JsonFragBox.index_of_from(s, "\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
// 2) Discover loop variable name from cond
local varname = LoopScanBox.find_loop_var_name(s, k_cmp)
if varname == null { return null }
// 3) Extract limit from Compare (i < limit pattern)
local k_op = JsonFragBox.index_of_from(s, "\"op\":", k_cmp)
if k_op < 0 { return null }
local op = JsonFragBox.read_string_after(s, k_op + 5)
if op == null { return null }
// Check for lhs Var pattern (i on left side)
local has_lhs_i = JsonFragBox.index_of_from(s, "\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
if !has_lhs_i { return null }
// Extract limit from rhs Int
local k_rhs = JsonFragBox.index_of_from(s, "\"rhs\":{", k_cmp)
if k_rhs < 0 { return null }
local k_ti = JsonFragBox.index_of_from(s, "\"type\":\"Int\"", k_rhs)
if k_ti < 0 { return null }
local k_v = JsonFragBox.index_of_from(s, "\"value\":", k_ti)
if k_v < 0 { return null }
local limit = JsonFragBox.read_int_after(s, k_v + 8)
if limit == null { return null }
// 4) Check for multiple Local/Assign in Loop body (multi-carrier indicator)
local k_body = JsonFragBox.index_of_from(s, "\"body\":[", k_loop)
if k_body < 0 { return null }
// Count Local declarations within this Loop's body
local local_count = 0
local search_pos = k_body
loop(local_count < 5) {
local k_local = JsonFragBox.index_of_from(s, "\"type\":\"Local\"", search_pos)
if k_local < 0 { break }
// Ensure it's within the current Loop body (not a nested Loop)
local next_loop = JsonFragBox.index_of_from(s, "\"type\":\"Loop\"", k_local)
if next_loop >= 0 && next_loop < k_local + 200 { break }
local_count = local_count + 1
search_pos = k_local + 1
}
// Multi-carrier requires at least 2 additional variables (besides loop var)
// e.g. fibonacci: a, b (+ implicit i)
if local_count < 2 {
if BuilderConfigBox.trace_enabled() == 1 {
print("[mirbuilder/internal/loop:multi_carrier:insufficient_carriers:count=" + local_count + "]")
}
return null
}
// 5) Build opts for multi_count mode
local opts = LoopOptsBox.new_map()
opts = LoopOptsBox.put(opts, "mode", "multi_count")
opts = LoopOptsBox.put(opts, "limit", limit)
// Carriers: default to [0, 1] for fibonacci pattern
// TODO: extract initial values from JSON (future enhancement)
local carriers = new ArrayBox()
carriers.push(0)
carriers.push(1)
opts = LoopOptsBox.put(opts, "carriers", carriers)
if BuilderConfigBox.trace_enabled() == 1 {
print("[mirbuilder/internal/loop:multi_carrier:detected:limit=" + limit + ",carriers=2]")
}
// 6) Delegate to LoopFormBox.build2 via LoopOptsBox
return LoopOptsBox.build2(opts)
}
}

View File

@ -16,6 +16,8 @@ builder.internal.lower_load_store_local_box = "builder/internal/lower_load_store
builder.internal.lower_typeop_cast_box = "builder/internal/lower_typeop_cast_box.hako" builder.internal.lower_typeop_cast_box = "builder/internal/lower_typeop_cast_box.hako"
builder.internal.lower_typeop_check_box = "builder/internal/lower_typeop_check_box.hako" builder.internal.lower_typeop_check_box = "builder/internal/lower_typeop_check_box.hako"
builder.internal.lower_loop_simple_box = "builder/internal/lower_loop_simple_box.hako" builder.internal.lower_loop_simple_box = "builder/internal/lower_loop_simple_box.hako"
builder.internal.lower_loop_sum_bc_box = "builder/internal/lower_loop_sum_bc_box.hako"
builder.internal.lower_loop_multi_carrier_box = "builder/internal/lower_loop_multi_carrier_box.hako"
builder.internal.loop_opts_adapter_box = "builder/internal/loop_opts_adapter_box.hako" builder.internal.loop_opts_adapter_box = "builder/internal/loop_opts_adapter_box.hako"
builder.internal.builder_config_box = "builder/internal/builder_config_box.hako" builder.internal.builder_config_box = "builder/internal/builder_config_box.hako"
builder.internal.jsonfrag_normalizer_box = "builder/internal/jsonfrag_normalizer_box.hako" builder.internal.jsonfrag_normalizer_box = "builder/internal/jsonfrag_normalizer_box.hako"

View File

@ -249,7 +249,7 @@ static box LoopFormBox {
return null return null
} }
// Map-based builder: build2({ mode, init, limit, step, skip, break }) // Map-based builder: build2({ mode, init, limit, step, skip, break, carriers })
method build2(opts) { method build2(opts) {
if opts == null { return null } if opts == null { return null }
local mode = "" + opts.get("mode") local mode = "" + opts.get("mode")
@ -266,7 +266,81 @@ static box LoopFormBox {
return me.build_loop_count_param_ex(start_value, limit, step, cmp) return me.build_loop_count_param_ex(start_value, limit, step, cmp)
} }
if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) } if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) }
if mode == "multi_count" {
return me.build_loop_multi_carrier(opts)
}
print("[loopform/unsupported-mode] " + mode) print("[loopform/unsupported-mode] " + mode)
return null return null
} }
// Multi-carrier loop (fibonacci-style: a, b, i tracking)
// Shape: i from 0 to limit, with 2 additional carried variables (a, b)
// carriers param: [init_a, init_b] (e.g. [0, 1] for fibonacci)
method build_loop_multi_carrier(opts) {
local limit = opts.get("limit")
if limit == null { limit = 10 }
local carriers = opts.get("carriers")
local init_a = 0
local init_b = 1
if carriers != null && carriers.length() >= 2 {
init_a = carriers.get(0)
init_b = carriers.get(1)
}
// Preheader (block 0): init i=0, limit, a=init_a, b=init_b
local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, 0)) // r1 = 0 (i)
pre.push(MirSchemaBox.inst_const(2, limit)) // r2 = limit
pre.push(MirSchemaBox.inst_const(3, init_a)) // r3 = init_a
pre.push(MirSchemaBox.inst_const(4, init_b)) // r4 = init_b
pre.push(MirSchemaBox.inst_jump(1))
// Header (block 1): PHI(i), PHI(a), PHI(b), compare, branch
local header = new ArrayBox()
local phi_i_inc = new ArrayBox()
phi_i_inc.push(MirSchemaBox.phi_incoming(0, 1)) // from preheader
phi_i_inc.push(MirSchemaBox.phi_incoming(3, 17)) // from latch
header.push(MirSchemaBox.inst_phi(10, phi_i_inc)) // r10 = i
local phi_a_inc = new ArrayBox()
phi_a_inc.push(MirSchemaBox.phi_incoming(0, 3)) // from preheader
phi_a_inc.push(MirSchemaBox.phi_incoming(3, 18)) // from latch
header.push(MirSchemaBox.inst_phi(11, phi_a_inc)) // r11 = a
local phi_b_inc = new ArrayBox()
phi_b_inc.push(MirSchemaBox.phi_incoming(0, 4)) // from preheader
phi_b_inc.push(MirSchemaBox.phi_incoming(3, 19)) // from latch
header.push(MirSchemaBox.inst_phi(12, phi_b_inc)) // r12 = b
header.push(MirSchemaBox.inst_compare("Lt", 10, 2, 13)) // r13 = (i < limit)
header.push(MirSchemaBox.inst_branch(13, 2, 4)) // body or exit
// Body (block 2): t = a + b; a' = b; b' = t; i' = i + 1
local body = new ArrayBox()
body.push(MirSchemaBox.inst_binop("Add", 11, 12, 14)) // r14 = a + b (t)
body.push(MirSchemaBox.inst_const(20, 1)) // r20 = step (1)
body.push(MirSchemaBox.inst_binop("Add", 10, 20, 15)) // r15 = i + 1
body.push(MirSchemaBox.inst_jump(3))
// Latch (block 3): pass updated values (i', a'=b, b'=t) back to header
local latch = new ArrayBox()
latch.push(MirSchemaBox.inst_copy(15, 17)) // r17 = i'
latch.push(MirSchemaBox.inst_copy(12, 18)) // r18 = a' (=b)
latch.push(MirSchemaBox.inst_copy(14, 19)) // r19 = b' (=t)
latch.push(MirSchemaBox.inst_jump(1))
// Exit (block 4): return final b value
local exit = new ArrayBox()
exit.push(MirSchemaBox.inst_ret(12))
// Assemble blocks
local blocks = new ArrayBox()
blocks.push(MirSchemaBox.block(0, pre))
blocks.push(MirSchemaBox.block(1, header))
blocks.push(MirSchemaBox.block(2, body))
blocks.push(MirSchemaBox.block(3, latch))
blocks.push(MirSchemaBox.block(4, exit))
return MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
}
} }

View File

@ -155,6 +155,8 @@ path = "lang/src/shared/common/string_helpers.hako"
"lang.mir.builder.internal.lower_typeop_cast_box" = "lang/src/mir/builder/internal/lower_typeop_cast_box.hako" "lang.mir.builder.internal.lower_typeop_cast_box" = "lang/src/mir/builder/internal/lower_typeop_cast_box.hako"
"lang.mir.builder.internal.lower_typeop_check_box" = "lang/src/mir/builder/internal/lower_typeop_check_box.hako" "lang.mir.builder.internal.lower_typeop_check_box" = "lang/src/mir/builder/internal/lower_typeop_check_box.hako"
"lang.mir.builder.internal.lower_loop_simple_box" = "lang/src/mir/builder/internal/lower_loop_simple_box.hako" "lang.mir.builder.internal.lower_loop_simple_box" = "lang/src/mir/builder/internal/lower_loop_simple_box.hako"
"lang.mir.builder.internal.lower_loop_sum_bc_box" = "lang/src/mir/builder/internal/lower_loop_sum_bc_box.hako"
"lang.mir.builder.internal.lower_loop_multi_carrier_box" = "lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako"
"lang.mir.builder.internal.loop_opts_adapter_box" = "lang/src/mir/builder/internal/loop_opts_adapter_box.hako" "lang.mir.builder.internal.loop_opts_adapter_box" = "lang/src/mir/builder/internal/loop_opts_adapter_box.hako"
"lang.mir.builder.internal.builder_config_box" = "lang/src/mir/builder/internal/builder_config_box.hako" "lang.mir.builder.internal.builder_config_box" = "lang/src/mir/builder/internal/builder_config_box.hako"
"lang.mir.builder.internal.jsonfrag_normalizer_box" = "lang/src/mir/builder/internal/jsonfrag_normalizer_box.hako" "lang.mir.builder.internal.jsonfrag_normalizer_box" = "lang/src/mir/builder/internal/jsonfrag_normalizer_box.hako"

View File

@ -0,0 +1,75 @@
#!/usr/bin/env bash
# selfhost_cli_run_basic_vm.sh
# - Canary for HakoCli.run lowering in CliRunLowerBox.
# - Uses HAKO_MIR_BUILDER_CLI_RUN=1 + HAKO_SELFHOST_BUILDER_FIRST=1 to force
# selfhost builder to emit MIR(JSON) for a small HakoCli.run sample.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
SRC_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$SRC_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$SRC_HAKO" <<'HAKO'
static box HakoCli {
method run(args){
@argc = 0
if args { argc = args.size() }
if argc == 0 { return 1 }
@cmd_raw = args.get(0)
@cmd = "" + cmd_raw
if cmd == "run" { return me.cmd_run(args) }
if cmd == "build"{ return me.cmd_build(args) }
if cmd == "emit" { return me.cmd_emit(args) }
if cmd == "check"{ return me.cmd_check(args) }
return 2
}
method cmd_run(args){ return 10 }
method cmd_build(args){ return 11 }
method cmd_emit(args){ return 12 }
method cmd_check(args){ return 13 }
}
static box Main {
method main(args){
@cli = new HakoCli()
return cli.run(args)
}
}
HAKO
set +e
HAKO_SELFHOST_BUILDER_FIRST=1 \
HAKO_MIR_BUILDER_FUNCS=1 \
HAKO_MIR_BUILDER_CLI_RUN=1 \
HAKO_SELFHOST_TRACE=1 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$SRC_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_cli_run_basic_vm (MIR generation failed rc=$rc)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
if ! grep -q '"name":"HakoCli.run/2"' "$OUT_MIR"; then
echo "[FAIL] selfhost_cli_run_basic_vm (HakoCli.run/2 not present in MIR)" >&2
cat "$OUT_MIR" >&2
exit 1
fi
if ! grep -q '"method":"cmd_run"' "$OUT_MIR"; then
echo "[FAIL] selfhost_cli_run_basic_vm (cmd_run not found in HakoCli.run MIR)" >&2
cat "$OUT_MIR" >&2
exit 1
fi
echo "[PASS] selfhost_cli_run_basic_vm (HakoCli.run lowered by selfhost builder)"
exit 0

View File

@ -0,0 +1,94 @@
#!/usr/bin/env bash
# selfhost_cli_run_exec_vm.sh
# - Execute the MIR generated by selfhost HakoCli.run lowering via VM,
# and verify that subcommand dispatch returns the expected codes.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
SRC_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$SRC_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$SRC_HAKO" <<'HAKO'
static box HakoCli {
method run(args){
@argc = 0
if args { argc = args.size() }
if argc == 0 { return 1 }
@cmd_raw = args.get(0)
@cmd = "" + cmd_raw
if cmd == "run" { return me.cmd_run(args) }
if cmd == "build"{ return me.cmd_build(args) }
if cmd == "emit" { return me.cmd_emit(args) }
if cmd == "check"{ return me.cmd_check(args) }
return 2
}
method cmd_run(args){ return 10 }
method cmd_build(args){ return 11 }
method cmd_emit(args){ return 12 }
method cmd_check(args){ return 13 }
}
static box Main {
method main(args){
@cli = new HakoCli()
return cli.run(args)
}
}
HAKO
# 1) Emit MIR(JSON) with selfhost builder (cli_run lowering enabled).
set +e
HAKO_SELFHOST_BUILDER_FIRST=1 \
HAKO_MIR_BUILDER_FUNCS=1 \
HAKO_MIR_BUILDER_CLI_RUN=1 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$SRC_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_cli_run_exec_vm (MIR generation failed rc=$rc)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
if ! grep -q '"name":"HakoCli.run/2"' "$OUT_MIR"; then
echo "[FAIL] selfhost_cli_run_exec_vm (HakoCli.run/2 not present in MIR)" >&2
cat "$OUT_MIR" >&2
exit 1
fi
# 2) Execute MIR via VM with different args and check exit codes.
NYASH_BIN="${NYASH_BIN:-$ROOT_DIR/target/release/hakorune}"
run_case() {
local label="$1"; shift
local expect="$1"; shift
local args=("$@")
set +e
NYASH_JSON_ONLY=0 NYASH_CLI_VERBOSE=0 \
"$NYASH_BIN" --backend vm "$SRC_HAKO" -- "${args[@]}" >/dev/null 2>&1
local rc=$?
set -e
if [ "$rc" -ne "$expect" ]; then
echo "[FAIL] selfhost_cli_run_exec_vm ($label: rc=$rc, expect=$expect)" >&2
return 1
fi
return 0
}
run_case "argc=0" 1 || exit 1
run_case "cmd=run" 10 "run" || exit 1
run_case "cmd=build" 11 "build" || exit 1
run_case "cmd=emit" 12 "emit" || exit 1
run_case "cmd=check" 13 "check" || exit 1
run_case "cmd=unknown" 2 "unknown" || exit 1
echo "[PASS] selfhost_cli_run_exec_vm (HakoCli.run dispatch matches expected exit codes)"
exit 0

View File

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# selfhost_mir_extern_codegen_basic_provider_vm.sh
# - Provider-first baseline for Phase 25.1b ExternCall coverage。
# - selfhost_mir_extern_codegen_basic_vm.sh と同じ mini .hako を使い、
# Rust provider 経路で env.codegen.emit_object externcall が出ているか確認する。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
TEST_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$TEST_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$TEST_HAKO" <<'HAKO'
static box TestBox {
emit_obj(args) {
return hostbridge.extern_invoke("env.codegen", "emit_object", args)
}
}
static box Main {
main(args) {
local t = new TestBox()
local a = new ArrayBox()
a.push(1)
return t.emit_obj(a)
}
}
HAKO
set +e
HAKO_SELFHOST_BUILDER_FIRST=0 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$TEST_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_mir_extern_codegen_basic_provider_vm (MIR generation failed rc=$rc)" >&2
echo "=== LOG OUTPUT ===" >&2
cat "$LOG_OUT" >&2
exit 1
fi
if ! grep -q '"op":"externcall"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_extern_codegen_basic_provider_vm (externcall op not present; skipping)" >&2
exit 0
fi
if ! grep -q '"func":"env.codegen.emit_object"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_extern_codegen_basic_provider_vm (env.codegen.emit_object externcall not found; skipping)" >&2
exit 0
fi
echo "[PASS] selfhost_mir_extern_codegen_basic_provider_vm (provider path lowers env.codegen.emit_object as externcall)"
exit 0

View File

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# selfhost_mir_extern_codegen_basic_vm.sh
# - Canary for Phase 25.1b Step4 (ExternCall coverage):
# ensure ExternCallLowerBox can lower
# hostbridge.extern_invoke(\"env.codegen\",\"emit_object\", args)
# to a MIR v1 externcall env.codegen.emit_object.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
TEST_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$TEST_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$TEST_HAKO" <<'HAKO'
static box TestBox {
emit_obj(args) {
return hostbridge.extern_invoke("env.codegen", "emit_object", args)
}
}
static box Main {
main(args) {
local t = new TestBox()
local a = new ArrayBox()
a.push(1)
return t.emit_obj(a)
}
}
HAKO
set +e
HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_MIR_BUILDER_FUNCS=1 HAKO_SELFHOST_TRACE=1 NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$TEST_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
# Check MIR(JSON) was generated and contains externcall env.codegen.emit_object
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_mir_extern_codegen_basic_vm (MIR generation failed rc=$rc)" >&2
echo "=== LOG OUTPUT ===" >&2
cat "$LOG_OUT" >&2
exit 1
fi
if ! grep -q '"op":"externcall"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_extern_codegen_basic_vm (externcall not present yet; selfhost builder externs not wired for this shape)" >&2
exit 0
fi
if ! grep -q '"func":"env.codegen.emit_object"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_extern_codegen_basic_vm (env.codegen.emit_object externcall not found; skipping)" >&2
exit 0
fi
echo "[PASS] selfhost_mir_extern_codegen_basic_vm (hostbridge.extern_invoke lowered to externcall env.codegen.emit_object)"
exit 0

View File

@ -0,0 +1,59 @@
#!/usr/bin/env bash
# selfhost_mir_methodcall_basic_provider_vm.sh
# - Provider-first baseline for Phase 25.1b MethodCall coverage。
# - 同じ mini .hako を selfhost 版と共有し、Rust provider 経路で
# ArrayBox.size 呼び出しが正常に MIR(JSON) に落ちているかを確認する。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
TEST_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$TEST_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$TEST_HAKO" <<'HAKO'
static box TestBox {
size_of_args(args) {
return args.size()
}
}
static box Main {
main(args) {
local t = new TestBox()
return t.size_of_args(args)
}
}
HAKO
set +e
HAKO_SELFHOST_BUILDER_FIRST=0 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$TEST_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_mir_methodcall_basic_provider_vm (MIR generation failed rc=$rc)" >&2
echo "=== LOG OUTPUT ===" >&2
cat "$LOG_OUT" >&2
exit 1
fi
# provider 経路では boxcall 形で size が出ていることを軽く確認
if ! grep -q '"op":"boxcall"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_methodcall_basic_provider_vm (no boxcall op found; shape may have changed)" >&2
exit 0
fi
if ! grep -q '"method":"size"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_methodcall_basic_provider_vm (ArrayBox.size not found; skipping)" >&2
exit 0
fi
echo "[PASS] selfhost_mir_methodcall_basic_provider_vm (provider path lowers args.size via boxcall)"
exit 0

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# selfhost_mir_methodcall_basic_vm.sh
# - Canary for Phase 25.1b Step4 (MethodCall coverage):
# ensure FuncBodyBasicLowerBox._try_lower_return_method can lower
# simple ArrayBox.size/get patterns to mir_call(Method).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || (cd "$SCRIPT_DIR/../../../../../../.." && pwd))"
TEST_HAKO="$(mktemp --suffix .hako)"
OUT_MIR="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$TEST_HAKO" "$OUT_MIR" "$LOG_OUT" || true' EXIT
cat > "$TEST_HAKO" <<'HAKO'
static box TestBox {
size_of_args(args) {
return args.size()
}
}
static box Main {
main(args) {
local t = new TestBox()
return t.size_of_args(args)
}
}
HAKO
set +e
HAKO_SELFHOST_BUILDER_FIRST=1 HAKO_MIR_BUILDER_FUNCS=1 HAKO_SELFHOST_TRACE=1 NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$TEST_HAKO" "$OUT_MIR" >"$LOG_OUT" 2>&1
rc=$?
set -e
# Check MIR(JSON) was generated and contains mir_call
if [ $rc -ne 0 ] || [ ! -s "$OUT_MIR" ]; then
echo "[FAIL] selfhost_mir_methodcall_basic_vm (MIR generation failed rc=$rc)" >&2
echo "=== LOG OUTPUT ===" >&2
cat "$LOG_OUT" >&2
exit 1
fi
if ! grep -q '"op":"mir_call"' "$OUT_MIR"; then
echo "[SKIP] selfhost_mir_methodcall_basic_vm (mir_call not present yet; selfhost builder funcs not wired for this shape)" >&2
exit 0
fi
echo "[PASS] selfhost_mir_methodcall_basic_vm (ArrayBox.size lowered to mir_call(Method))"
exit 0

View File

@ -0,0 +1,55 @@
#!/usr/bin/env bash
# stage1_launcher_program_to_mir_selfhost_vm.sh
# - Canary for Phase 25.1b: ensure StageB + selfhost builder (MirBuilderBox)
# sees Stage1 CLI launcher (HakoCli.run) and still produces MIR(JSON)
# when selfhost-first is enabled (provider fallback可)。
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || cd "$SCRIPT_DIR/../../../../../../.." && pwd)"
if [ ! -f "$ROOT_DIR/lang/src/runner/launcher.hako" ]; then
echo "[SKIP] stage1_launcher_program_to_mir_selfhost_vm (launcher.hako missing)"
exit 0
fi
source "$ROOT_DIR/tools/smokes/v2/lib/test_runner.sh" || true
require_env || { echo "[SKIP] env not ready"; exit 0; }
SRC="$ROOT_DIR/lang/src/runner/launcher.hako"
OUT_JSON="$(mktemp --suffix .json)"
LOG_OUT="$(mktemp --suffix .log)"
trap 'rm -f "$OUT_JSON" "$LOG_OUT" || true' EXIT
set +e
HAKO_SELFHOST_BUILDER_FIRST=1 \
HAKO_MIR_BUILDER_FUNCS=1 \
HAKO_SELFHOST_TRACE=1 \
NYASH_JSON_ONLY=1 \
bash "$ROOT_DIR/tools/hakorune_emit_mir.sh" "$SRC" "$OUT_JSON" >"$LOG_OUT" 2>&1
rc=$?
set -e
if [ $rc -ne 0 ] || [ ! -s "$OUT_JSON" ]; then
echo "[FAIL] stage1_launcher_program_to_mir_selfhost_vm (Program→MIR failed rc=$rc)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
# selfhost builder 側の観測タグが出ているか確認する(構造チェック)
if ! grep -q "\[builder/cli:entry_detected\]" "$LOG_OUT"; then
echo "[FAIL] stage1_launcher_program_to_mir_selfhost_vm (missing [builder/cli:entry_detected] tag)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
if ! grep -q "\[builder/cli:run_shape\]" "$LOG_OUT"; then
echo "[FAIL] stage1_launcher_program_to_mir_selfhost_vm (missing [builder/cli:run_shape] tag)" >&2
sed -n '1,80p' "$LOG_OUT" >&2 || true
exit 1
fi
echo "[PASS] stage1_launcher_program_to_mir_selfhost_vm"
exit 0