From 5f06d82ee5a50130d8ffc2f65ced93369df7d58a Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Sun, 16 Nov 2025 03:11:49 +0900 Subject: [PATCH] =?UTF-8?q?Phase=2025.1b:=20Step=20B=E5=AE=8C=E4=BA=86?= =?UTF-8?q?=EF=BC=88multi-carrier=20LoopForm=E5=AF=BE=E5=BF=9C=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CURRENT_TASK.md | 5 +- .../roadmap/phases/phase-25.1b/README.md | 341 +++++++++++++++++- .../builder/func_body/basic_lower_box.hako | 199 +++++++++- .../mir/builder/func_body/cli_entry_box.hako | 43 +++ .../builder/func_body/cli_run_lower_box.hako | 273 ++++++++++++++ .../builder/func_body/cli_run_shape_box.hako | 49 +++ .../builder/func_body/extern_call_box.hako | 141 ++++++++ lang/src/mir/builder/func_lowering.hako | 29 ++ .../lower_loop_multi_carrier_box.hako | 99 +++++ lang/src/mir/hako_module.toml | 2 + lang/src/shared/mir/loop_form_box.hako | 76 +++- nyash.toml | 2 + .../phase251/selfhost_cli_run_basic_vm.sh | 75 ++++ .../core/phase251/selfhost_cli_run_exec_vm.sh | 94 +++++ ...st_mir_extern_codegen_basic_provider_vm.sh | 60 +++ .../selfhost_mir_extern_codegen_basic_vm.sh | 60 +++ ...lfhost_mir_methodcall_basic_provider_vm.sh | 59 +++ .../selfhost_mir_methodcall_basic_vm.sh | 52 +++ ...ge1_launcher_program_to_mir_selfhost_vm.sh | 55 +++ 19 files changed, 1700 insertions(+), 14 deletions(-) create mode 100644 lang/src/mir/builder/func_body/cli_entry_box.hako create mode 100644 lang/src/mir/builder/func_body/cli_run_lower_box.hako create mode 100644 lang/src/mir/builder/func_body/cli_run_shape_box.hako create mode 100644 lang/src/mir/builder/func_body/extern_call_box.hako create mode 100644 lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_exec_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_provider_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_provider_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_vm.sh create mode 100644 tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_selfhost_vm.sh diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 1a080b67..792ff376 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -106,7 +106,10 @@ Update (2025-11-15 — Phase 25.1a: Stage1 build pipeline hotfix progress) - JSON 出力を jsonfrag ベースで構築し、functions 配列に複数関数を格納可能にする。 - 上記の完了後、selfhost-first を既定に戻し Stage1 CLI EXE の JSON stdout 契約(Rust/llvmlite と同等)を仕上げる。 - ループについては LoopForm 正規化を前提とし、LoopForm の制約を満たさない形(キャリア3変数以上・順序再配置不能など)は selfhost builder では扱わず、タグ付き Fail‑Fast で検知する(PHI ノード生成は既存 LoopForm/LowerLoop helper に一元化)。 - - docs: `docs/development/roadmap/phases/phase-25.1b/README.md` を design deep‑dive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針(Fail‑Fast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序など)を整理済み。Step0(Fail‑Fast/観測導線)は 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` から段階的に呼び出せるようにした。Step3(LoopForm対応)も 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 deep‑dive 版に更新し、FuncLoweringBox / MirBuilderBox の現状把握と拡張方針(Fail‑Fast ポリシー・LoopForm/PHI ポリシー・Step0〜6 の実装順序など)を整理済み。Step0(Fail‑Fast/観測導線)は 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` から段階的に呼び出せるようにした。Step3(LoopForm対応)も 2025-11-15 実装完了:`FuncBodyBasicLowerBox._try_lower_loop` から `LowerLoopSumBcBox`/`LowerLoopSimpleBox` を呼び、LoopForm正規化済みループをMIRに落とす導線を確立、PHI/キャリア処理は既存Loop lowersに完全委譲、制約外は `[builder/funcs:unsupported:loopform]` でFail-Fast。Step4(MethodCall/ExternCall パリティ)は Rust 層 (`builder_calls.rs` / extern handler) と Stage‑B Program(JSON) の形を読み解いた設計メモに基づき、`FuncBodyBasicLowerBox._try_lower_return_method` による ArrayBox.size/get(params ベース 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 は Stage‑B/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 — Stage‑B using resolver alias 化 & 環境導線) - Stage‑B 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`) に置き換え、Stage‑3 パーサが string literal using を弾いていた問題を解消。 diff --git a/docs/development/roadmap/phases/phase-25.1b/README.md b/docs/development/roadmap/phases/phase-25.1b/README.md index 78d96993..4b49bbde 100644 --- a/docs/development/roadmap/phases/phase-25.1b/README.md +++ b/docs/development/roadmap/phases/phase-25.1b/README.md @@ -1,6 +1,6 @@ # Phase 25.1b — Selfhost Builder Parity (Planning → Design Deep‑Dive) -Status: planning-only(実装着手前の設計・分析フェーズ) +Status: Step0〜3 実装済み・Step4(Method/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` 等をファイル結合前に解決。 - Stage‑B entry 側は string literal using を廃止し、`using lang.compiler.entry.using_resolver as Stage1UsingResolverBox` のように module alias を使用する。 +#### Stage‑B の安定度と使用上の注意 + +- 正規経路: + - Stage‑B は `tools/hakorune_emit_mir.sh` / `tools/selfhost/selfhost_build.sh` 経由で呼び出すことを前提としており、これらのラッパが Stage‑3 用 ENV(`NYASH_PARSER_STAGE3=1` / `HAKO_PARSER_STAGE3=1` / `NYASH_PARSER_ALLOW_SEMICOLON=1` など)を一括でセットする。 + - Phase 25.1b では「このラッパ経由で呼ぶ限り Stage‑B 自体は安定」とみなし、主な改善対象を Program→MIR の selfhost builder 側に置く。 +- 手動実行時の注意: + - Stage‑3 ENV を立てずに Stage‑B / VM を直接叩くと、`Undefined variable: local` のようなエラーが発生するが、これは構文/実装バグではなく「Stage‑3 キーワード(local など)を Stage‑1 と同じルールでパースしてしまっている」ため。 + - 詳細な原因と対処は `docs/development/troubleshooting/stage3-local-keyword-guide.md` にまとめてあり、selfhost 開発では「まずラッパスクリプトを使う → 必要な場合のみ ENV を明示して直叩きする」方針とする。 + ### Rust provider (`env.mirbuilder.emit`) - `program_json_to_mir_json_with_imports`: @@ -47,6 +56,10 @@ Status: planning-only(実装着手前の設計・分析フェーズ) - どの `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`)へフォールバックする。 +- 現時点での不足点(要約・2025-11-16): + - `FuncBodyBasicLowerBox` が本番でカバーできているのは、Local/If/Return の基本形+LoopForm 正規化済み Loop+ごく一部の MethodCall(`args.size/get` と `String.length` の Return 形)に限られる。Stage1 CLI のような複雑な関数本体(複数 Local+If ネスト+Loop+Method/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` の run(usage/unknown ログ付き)は shape mismatch で selfhost 降ろしの対象外。ここを Stage1 実形に合わせて広げることが Phase 25.1b の中心タスクになっている。 ### Stage1 EXE / build_stage1.sh の現状 @@ -91,6 +104,36 @@ Status: planning-only(実装着手前の設計・分析フェーズ) - まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/for)から対応し、 - LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの)は `[builder/selfhost-first:unsupported:loopform]` タグなどで Fail‑Fast する。 +#### 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 canary(rc チェック)で確認する。 +- ガード: + - 新しい 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) 1. 調査: @@ -191,13 +234,228 @@ Status: planning-only(実装着手前の設計・分析フェーズ) - `cmd_build_exe`の`loop(i < argc)`等、Stage1 CLIの代表的なwhile/forパターンをselfhost builderで通せる基礎が整った。 - 次のステップ: LoopForm対応の動作確認スモークテスト追加、Step4(MethodCall/ExternCall)へ進む。 -### Step 4 — MethodCall / ExternCall パリティ -- 目的: `hostbridge.extern_invoke` / `FileBox` / `ArrayBox` など Stage1 CLI で多用される呼び出しを selfhost builder でも再現。 +### Step 4 — MethodCall / ExternCall パリティ(設計メモ・Rust層読解込み) +- Status: design-only(Rust 層の挙動を踏まえた設計まで) +- 目的: `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 メソッド呼び出し) + - Stage‑B 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-call(me.cmd_*)の解決** + - Stage1 CLI の `me.cmd_*` は、Stage‑B の 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. **ExternCall(hostbridge.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` を生成。 - - `func_map` により `me.cmd_build_exe` のような self-call も Box.method/N に resolve(未対応の呼び出しは `[builder/funcs:unsupported:call]` で Fail)。 -- 成果物: - - Stage1 CLI の主要メソッド体が selfhost builder で MIR 関数化できる。 + - `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 はあくまで ring1(Stage1 CLI ソース)の Program(JSON v0) を観測するだけで、ring0(Rust 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) から把握し、将来の専用 lower(CliRunLowerBox)が安全に使えるかを事前に観測する。 +- 作業: + - `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=` + を出力し、戻り値として `{"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 と同様、ring1(Stage1 Hako CLI)の構造を観測する箱のみを追加する。 + - MIR 生成はまだ Rust provider /既存 lower に任せており、ring0 の責務(env.* extern や実行エンジン)にも影響を与えない。 + - 専用 lower(`CliRunLowerBox` が実際に MIR を返す形)は、Stage‑B 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 にフォールバックする(Fail‑Fast)。 +- 対象とする 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. タグと Fail‑Fast(実装済み) + - 形が完全に一致し、`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 本体には適用していない(今後 Stage‑B Program(JSON v0) を詳細に比較しながら対応範囲を広げる)。 + +#### Stage1 HakoCli.run(本番)とのギャップ整理(provider MIR ベース) + +現状、このホスト環境では Stage‑B 実行中に `Undefined variable: local`(Stage‑3 キーワード)で 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 [options]` を `print` してから `ret 1`。 +- サブコマンド unknown の場合: + - MVP: 単純に `return 2`。 + - 実装: `[hakorune] unknown command: ` を `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 に寄せる予定)。 + +今後 Stage‑B Program(JSON v0) が安定して取れるようになったら、上記の差分を JSON レベルで再確認し、 +- usage/unknown の `print` ブロックを「前置/後置のサイドエフェクト」として `_check_shape` の許容パターンに追加するか、 +- あるいは run 本体を「MVP サブセット(引数分岐)+印字専用ブロック」に分けて扱うか、 +を決める予定。 + +#### Stage1 用 run パターン拡張方針(設計) + +Stage1 launcher.hako の本番 `HakoCli.run` を selfhost lowering の対象に含めるための方針は次のとおり: + +- 目的: + - Rust CLI(Stage0)と同じ意味論(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: ` を出力してから `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)でガードし、 + かつ Stage‑B Program(JSON v0) で `check_core_shape` / `check_logging_shape` の両方を満たしている場合だけ有効にする。 + - それ以外のケース(print の形がずれている / 追加の case が増えたなど)は、今まで通り provider 経路に退避する。 ### Step 5 — call resolve / methodize 後処理 - 目的: 生成済み 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` が通ることを確認。 - 問題が無ければ `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/get(params ベース receiver)と StringBox.length(引数なし)を最小カバー済み。生成される MIR は `mir_call { callee: { type: "Method", box_name: "", 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 は Stage‑B の 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 で実際に使われているメソッドは、FileBox(open/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 builder(provider-first)経路を確認するスモーク。 + - `*_selfhost_vm.sh` … 同じ .hako / 期待 rc を selfhost-first(`HAKO_SELFHOST_BUILDER_FIRST=1`)で確認するスモーク。 + - ループ系: + - 例として `phase2100` の LoopForm/PHI canary(Rust ベース)に対応して、 + - `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 プロファイルへの昇格を検討する。 + +*** diff --git a/lang/src/mir/builder/func_body/basic_lower_box.hako b/lang/src/mir/builder/func_body/basic_lower_box.hako index 357f2c0c..4690c8aa 100644 --- a/lang/src/mir/builder/func_body/basic_lower_box.hako +++ b/lang/src/mir/builder/func_body/basic_lower_box.hako @@ -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_loop_simple_box" as LowerLoopSimpleBox 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 { lower(func_name, box_name, params_arr, body_json) { @@ -34,7 +35,13 @@ static box FuncBodyBasicLowerBox { return null } - // 2) Non-Loop processing: Local/If/Return patterns only + // 2) Try simple Return(Method ...) pattern(params ベースの 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) if lowered != null { return lowered } @@ -353,19 +360,203 @@ static box FuncBodyBasicLowerBox { 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 array(Int 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) { // 1) LoopForm の Loop を持つ Program(JSON) であることを軽く確認 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) 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) if out2 != null { return me._rebind(out2, func_name, box_name, params_arr, "[funcs/basic:loop.simple]") } } - // 4) ここまででダメなら selfhost 側では未対応 + // 5) ここまででダメなら selfhost 側では未対応 return null } } diff --git a/lang/src/mir/builder/func_body/cli_entry_box.hako b/lang/src/mir/builder/func_body/cli_entry_box.hako new file mode 100644 index 00000000..58896a76 --- /dev/null +++ b/lang/src/mir/builder/func_body/cli_entry_box.hako @@ -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 + } +} + diff --git a/lang/src/mir/builder/func_body/cli_run_lower_box.hako b/lang/src/mir/builder/func_body/cli_run_lower_box.hako new file mode 100644 index 00000000..fb4ee02a --- /dev/null +++ b/lang/src/mir/builder/func_body/cli_run_lower_box.hako @@ -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 Stage‑B 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 1(Compare と 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 + } +} diff --git a/lang/src/mir/builder/func_body/cli_run_shape_box.hako b/lang/src/mir/builder/func_body/cli_run_shape_box.hako new file mode 100644 index 00000000..dee0d82c --- /dev/null +++ b/lang/src/mir/builder/func_body/cli_run_shape_box.hako @@ -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 + } +} + diff --git a/lang/src/mir/builder/func_body/extern_call_box.hako b/lang/src/mir/builder/func_body/extern_call_box.hako new file mode 100644 index 00000000..021dd79d --- /dev/null +++ b/lang/src/mir/builder/func_body/extern_call_box.hako @@ -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 + } +} + diff --git a/lang/src/mir/builder/func_lowering.hako b/lang/src/mir/builder/func_lowering.hako index 06db78cb..95cbd07f 100644 --- a/lang/src/mir/builder/func_lowering.hako +++ b/lang/src/mir/builder/func_lowering.hako @@ -6,6 +6,10 @@ 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.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 { // Lower function definitions to MIR @@ -21,6 +25,13 @@ static box FuncLoweringBox { 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 local defs_idx = JsonFragBox.index_of_from(s, "\"defs\":", 0) 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) { 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) 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 local ret_idx = JsonFragBox.index_of_from(body_str, "\"type\":\"Return\"", 0) if ret_idx < 0 { return null } diff --git a/lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako b/lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako new file mode 100644 index 00000000..26ee3c1d --- /dev/null +++ b/lang/src/mir/builder/internal/lower_loop_multi_carrier_box.hako @@ -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) + } +} diff --git a/lang/src/mir/hako_module.toml b/lang/src/mir/hako_module.toml index 2d0cbebb..69bf0c38 100644 --- a/lang/src/mir/hako_module.toml +++ b/lang/src/mir/hako_module.toml @@ -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_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_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.builder_config_box = "builder/internal/builder_config_box.hako" builder.internal.jsonfrag_normalizer_box = "builder/internal/jsonfrag_normalizer_box.hako" diff --git a/lang/src/shared/mir/loop_form_box.hako b/lang/src/shared/mir/loop_form_box.hako index ebeba969..12af9f96 100644 --- a/lang/src/shared/mir/loop_form_box.hako +++ b/lang/src/shared/mir/loop_form_box.hako @@ -249,7 +249,7 @@ static box LoopFormBox { 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) { if opts == null { return null } local mode = "" + opts.get("mode") @@ -266,7 +266,81 @@ static box LoopFormBox { 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 == "multi_count" { + return me.build_loop_multi_carrier(opts) + } print("[loopform/unsupported-mode] " + mode) 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)) + } } diff --git a/nyash.toml b/nyash.toml index e0ff7dae..fae60b12 100644 --- a/nyash.toml +++ b/nyash.toml @@ -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_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_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.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" diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh new file mode 100644 index 00000000..8f4bdcd9 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh @@ -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 + diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_exec_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_exec_vm.sh new file mode 100644 index 00000000..80d3ef76 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_exec_vm.sh @@ -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 + diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_provider_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_provider_vm.sh new file mode 100644 index 00000000..e8e9ce4f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_provider_vm.sh @@ -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 + diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_vm.sh new file mode 100644 index 00000000..23569f8f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_extern_codegen_basic_vm.sh @@ -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 diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_provider_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_provider_vm.sh new file mode 100644 index 00000000..1c446e75 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_provider_vm.sh @@ -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 + diff --git a/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_vm.sh new file mode 100644 index 00000000..114b6708 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/selfhost_mir_methodcall_basic_vm.sh @@ -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 diff --git a/tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_selfhost_vm.sh b/tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_selfhost_vm.sh new file mode 100644 index 00000000..60917030 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_selfhost_vm.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# stage1_launcher_program_to_mir_selfhost_vm.sh +# - Canary for Phase 25.1b: ensure Stage‑B + 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 +