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:
@ -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=<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 と同様、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 <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 に寄せる予定)。
|
||||
|
||||
今後 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: <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)でガードし、
|
||||
かつ 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: "<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 は 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 プロファイルへの昇格を検討する。
|
||||
|
||||
***
|
||||
|
||||
Reference in New Issue
Block a user