Files
hakorune/docs/development/roadmap/phases/phase-25.1b/README.md
nyash-codex 7ca7f646de Phase 25.1b: Step2完了(FuncBodyBasicLowerBox導入)
Step2実装内容:
- FuncBodyBasicLowerBox導入(defs専用下請けモジュール)
- _try_lower_local_if_return実装(Local+単純if)
- _inline_local_ints実装(軽い正規化)
- minimal lowers統合(Return/BinOp/IfCompare/MethodArray系)

Fail-Fast体制確立:
- MirBuilderBox: defs_onlyでも必ずタグ出力
- [builder/selfhost-first:unsupported:defs_only]
- [builder/selfhost-first:unsupported:no_match]

Phase構造整備:
- Phase 25.1b README新設(Step0-3計画)
- Phase 25.2b README新設(次期計画)
- UsingResolverBox追加(using system対応準備)

スモークテスト:
- stage1_launcher_program_to_mir_canary_vm.sh追加

Next: Step3 LoopForm対応

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-15 22:32:13 +09:00

18 KiB
Raw Blame History

Phase 25.1b — Selfhost Builder Parity (Planning → Design DeepDive)

Status: planning-only実装着手前の設計・分析フェーズ

ゴール

  • Rust 側 Program→MIR (env.mirbuilder.emit) と Hakorune 側 selfhost builder (MirBuilderBox.emit_from_program_json_v0) の機能差を埋め、Stage1 CLIlauncher.hakoレベルの Program(JSON) を selfhost builder 単独で lowering できるようにする。
  • .hako → Program(JSON v0) → MIR(JSON) のうち、「Program→MIR」を selfhost builder だけでも成立させ、provider 経路はあくまで退避路に下げていく。
  • 最終的には HAKO_SELFHOST_BUILDER_FIRST=1 を既定に戻し、Stage1 CLI EXE の I/OJSON stdout + exit codeを Rust/llvmlite と同じ契約に揃える。

現状Phase 25.1a 時点)

StageBProgram(JSON v0) emit

  • compiler_stageb.hakodefs を含む Program(JSON v0) を出力できる:
    • HakoCli.run / HakoCli.cmd_emit_* / HakoCli.cmd_build_* などのメソッドを Program.defs 配列として含む。
    • FuncScannerBox HAKO_STAGEB_FUNC_SCAN=1 により、static box メソッド(暗黙の me 引数付き)も defs に載る。
  • using 解決:
    • Stage1UsingResolverBoxlang/src/compiler/entry/using_resolver_box.hako HAKO_STAGEB_MODULES_LISTnyash.toml[modules] を参照し、using lang.mir.builder.MirBuilderBox 等をファイル結合前に解決。
    • StageB entry 側は string literal using を廃止し、using lang.compiler.entry.using_resolver as Stage1UsingResolverBox のように module alias を使用する。

Rust provider (env.mirbuilder.emit)

  • program_json_to_mir_json_with_imports:
    • Program.bodyProgram.defs の両方を受理し、FuncDefV0 から func_map を構築して Call を解決。
    • HAKO_MIRBUILDER_IMPORTS 経由で MirBuilderBox / BuildBox などの static box 名をインポートし、Const(String(alias)) として扱う。
  • JSON v0 ブリッジ:
    • args を暗黙パラメータとして扱う修正済み(undefined variable: args は解消)。
    • hostbridge は wellknown 変数として Const(String("hostbridge")) を生成し、hostbridge.extern_invoke(...) を含む Program(JSON) でも undefined にならない。
  • 結果:
    • launcher.hako に対して ~62KB の MIR(JSON) を安定して生成できており、Phase 25.1a では provider 経路が事実上のメインルート。

selfhost builder (MirBuilderBox.emit_from_program_json_v0)

  • エントリ:
    • 入口で HAKO_MIR_BUILDER_FUNCS=1 のときに FuncLoweringBox.lower_func_defs(program_json, program_json) を呼び出し、defs 専用の MIR 関数群を文字列として受け取る(func_defs_mir)。
    • その後、internal 配下の多数の Lower*Box.try_lower を順番に適用し、Program 全体を 1 関数(main)ベースの MIR(JSON) に落とす。
  • FuncLoweringBox の現状:
    • lower_func_defs は Program(JSON) から defs 配列をパースし、各 def ごとに _lower_func_body を呼ぶ。
    • _lower_func_body がサポートするのは 単一の Return を持つ最小パターンのみ:
      • Return(Int)
      • Return(Binary(op, lhs, rhs))+,-,*,/ のみ、かつ Int/Var 組み合わせ限定)
      • Return(Call(name, args))Call 名は func_map を用いて Box.method に解決)
    • 複数ステートメント、IfLoop、メソッドチェインなど Stage1 CLI に実際に現れる構造はすべて null でスキップされる。
  • MirBuilderBox の挙動:
    • 何らかの Lower*Box が Program 全体にマッチした場合は、その MIR(JSON) に対して _norm_if_apply を通し、FuncLoweringBox.inject_funcs で defs 分の MIR 関数を 追加注入 する。
    • どの Lower*Box もマッチしないが func_defs_mir が非空の場合は、"{\"functions\":[" + defs + "]}" という最小モジュールを組み立てて _norm_if_apply に渡す。
      • このケースでは main 関数を含まない defs だけの MIR モジュールになり、ny-llvmc 側でエントリポイント不足や空挙動 EXE を生む原因になる。
    • func_defs_mir も空で internal lowers も不発の場合は null を返し、最後に provider delegateenv.mirbuilder.emit)へフォールバックする。

Stage1 EXE / build_stage1.sh の現状

  • tools/selfhost/build_stage1.sh:
    • 既定値 HAKO_SELFHOST_BUILDER_FIRST=0provider-firstでは、Stage1 EXE ビルドは成功し、emit program-json / emit mir-json / build exe の I/O も Rust/llvmlite と同等の JSON+exit code 契約を満たす。
    • HAKO_SELFHOST_BUILDER_FIRST=1selfhost-firstでは、Stage1 CLI のような複雑な Program(JSON) に対して selfhost builder が「defs のみ」MIR か mini stub MIR を返し、結果として「Result: 0 だけ出す空 EXE」になる。
  • スモーク:
    • tools/smokes/v2/profiles/quick/core/phase251/stage1_launcher_program_to_mir_canary_vm.sh は provider-first ルートのみをカバーしており、selfhost builder 単独経路のギャップは検出できていない(今後 canary を追加する必要がある)。

25.1b のスコープ(案)

  • selfhost builder 本体Hakorune 側):
    • Program.defs の処理を実装し、box + method ごとに MIR 関数を生成する。
      • 例: HakoCli.run / HakoCli.cmd_emit_program_json / HakoCli.cmd_emit_mir_json / HakoCli.cmd_build_exe 等。
    • call / BoxCall / ExternCall の解決(func_lowering + call resolve 相当)を Hakorune 側にも実装し、Call("cmd_emit_mir_json")Global callee に解決できるようにする。
    • Loop / branch / compare / Array/Map 操作など Stage1 CLI で出現する構造を包括的に lowering するため、lang/src/mir/builder/internal/* の helper を本番経路に統合する。
  • JSON 出力:
    • 現状の "{\"functions\":[...]}\" ベタ書きから、jsonfrag 等を用いた構造的出力に切り替え、複数関数を同一モジュールに含められるようにする。
    • 既存の mini パターン用 JSON 組み立てとの互換性を維持しつつ、Stage1 CLI で必要な関数数に耐えられる形に拡張する。
  • 運用ポリシー:
    • Phase 25.1b 中は HAKO_SELFHOST_BUILDER_FIRST=0 のままprovider-firstとし、selfhost builder が Stage1 CLI を lowering し切れることを確認した時点で =1 への切り替えを検討する。
    • lambda/FunctionBox (NewClosure 等) は本フェーズでは扱わず、従来どおり builder からは排除したままにする(別フェーズ 25.2b に委ねる)。

Guardrails / ポリシー

  • Rust Freeze Policy:
    • Rust 側の Program→MIR 実装には原則手を入れず、selfhost builder は「Rust の既存挙動に合わせる」方向で実装する。
    • 変更は Hakorune 側 (lang/src/mir/builder/*) とツール (tools/hakorune_emit_mir.sh) に閉じる。
  • FailFast:
    • selfhost builder が Program(JSON) の一部に対応していない場合は、明確なタグ付きで失敗させる(例: [builder/selfhost-first:unsupported:Match]ようにし、silent stub には戻さない。
    • provider 経路は退避路として残しつつ、Stage1 CLI の代表ケースでは selfhost builder が先に成功することを目標にする。

LoopForm / PHI ポリシー(重要メモ)

  • ループを含む関数本体を selfhost builder で扱うときは、LoopForm 正規化を前提にする:
    • 可能な限り docs/guides/loopform.md で定義された「キャリア1個の φ」モデルに従う。
    • break/continue を含むループは、LoopForm の制約更新変数最大2個・セグメント整列などを満たす範囲でのみ lowering 対象にする。
  • MirBuilder 側で「生の while/for を直接 MIR の PHI に落とす」ような adhoc 実装は行わない:
    • PHI ノードの生成・配置は既存の LoopForm/LowerLoop 系 helperloop_scan_box.hakolower_loop_*_box.hako などに一元化し、builder 本体はそれを利用する立場にとどめる。
    • LLVM harness 側の PHI 不変条件ブロック先頭グルーピングwelltyped incomingを崩さない。
  • Phase 25.1b では:
    • まず LoopForm 前提で安全に扱える最小のループ形(既存 selfhost テストでカバー済みの while/forから対応し、
    • LoopForm 未適用の複雑なループ(例: キャリア3変数以上・ネストが深いもの[builder/selfhost-first:unsupported:loopform] タグなどで FailFast する。

Next Steps実装フェーズに入るときの TODO

  1. 調査:
    • Rust 側 program_json_to_mir_json_with_imports の挙動をトレースし、どの AST ノードがどの MIR に降りているかを整理(特に defs/call/loop/boxcall
    • selfhost builder の現行 JSON 生成経路を洗い出し、stub を生成している箇所を特定。
  2. 設計:
    • Program.defs → MIR 関数生成のインタフェース(必要なフィールドと lowering 手順)を定義。
    • call resolve 用の軽量マップ(name -> qualified)を selfhost builder に導入する。
  3. 実装:
    • defs 対応・call resolve・loop/branch lowering を段階的に導入しつつ、各ステップで mini スモークを追加。
    • jsonfrag ベースの出力に切り替えながら、既存の mini テストを全て通ることを確認。
  4. 検証:
    • tools/hakorune_emit_mir.sh lang/src/runner/launcher.hako …HAKO_SELFHOST_BUILDER_FIRST=1 で実行し、62KB クラスの MIR(JSON) が selfhost builder 単独で得られることを確認。
    • tools/selfhost/build_stage1.sh を selfhost-first でビルドし、Stage1 CLI EXE が emit/build コマンドを正しく実行できるかJSON stdout + exit codeをスモークで検証。

設計 TODOFuncLoweringBox / MirBuilderBox 拡張の方向性)

※ ここから先は「具体的にどこをどう広げるか」の設計メモ。実装はまだ行わない。

  1. FuncLowering の対応範囲拡張

    • _lower_func_body を Stage1 CLI で実際に使われているパターンまで広げる:
      • 単一 Return だけでなく、ローカル変数定義if 分岐loop を含む「典型的な CLI ハンドラ」の形をサポート。
      • MethodCallargs.size() / args.get(i) / FileBox.open/read/write / ArrayBox.push など)を MIR mir_callcall に落とす処理を追加。
    • func_map / resolve_call_target を用いて、HakoCli.cmd_emit_* / cmd_build_exe などの内部 Call を Global("HakoCli.cmd_emit_*") 系に正規化。
  2. MirBuilder 本体の出力構造

    • これまでの「Program 全体を 1 関数 main に落とす」前提から、「Program.body + defs を multifunction MIR モジュールに落とす」前提へシフト:
      • Program.body からはエントリ Main.main/1 相当の MIR 関数を生成。
      • Program.defs からは HakoCli.* などの補助関数を生成。
    • _norm_if_apply / inject_funcs の役割を整理し、「main 関数を含まない defsonly モジュール」を返さないように FailFast する。
  3. FailFast とデバッグ

    • Stage1 CLI のような大きな Program(JSON) に対して selfhost builder が未対応の場合は:
      • [builder/selfhost-first:unsupported:func_body] などのタグ付きで明示的に失敗。
      • provider 経路(env.mirbuilder.emit)へのフォールバックは維持するが、「空 EXE になる stub MIR」は生成しない方針に切り替える。
    • HAKO_SELFHOST_TRACE=1 時に、FuncLowering がどの def でどこまで lowering できたかをログに出す。
  4. 検証計画

    • selfhostfirst canary:
      • stage1_launcher_program_to_mir_canary_vm.sh の selfhostfirst 版を追加し、HAKO_SELFHOST_BUILDER_FIRST=1 MirBuilderBox.emit_from_program_json_v0 だけで 60KB 級の MIR(JSON) を生成できることを確認。
    • Stage1 build:
      • tools/selfhost/build_stage1.sh を selfhost-first で回し、生成された Stage1 EXE に対して emit program-json / emit mir-json / build exe スモークを追加。

このファイルは引き続き「Phase 25.1b の計画メモ(設計ディープダイブ)」として扱い、実装は Phase 25.1a の安定化完了後に、小さな差分に分割して順次進める。***


実装計画(順番にやる TODO

備考: ここでは Phase 25.1b を「複数の最小ステップ」に分解して、順番/ゴール/ガードを具体的にメモしておくにゃ。

Step 0 — FailFast・観測を揃える

  • Status: implemented (2025-11-15). MirBuilderBox now tags defs_only / no_match failures and aborts, and FuncLoweringBox logs unsupported defs when HAKO_SELFHOST_TRACE=1.
  • 目的: 既存の selfhost builder がどこで諦めているかを正確に観測し、stub MIR を返さずに Fail させる導線を整える。
  • 作業:
    • MirBuilderBox.emit_from_program_json_v0
      • func_defs_mir だけが非空だった場合でも黙って { "functions": [defs] } を返さず、[builder/selfhost-first:unsupported:defs_only] を出す。
      • internal lowers がすべて null の場合、[builder/selfhost-first:unsupported:<reason>] のタグを付与。
    • FuncLoweringBox.lower_func_defs
      • どの関数名で _lower_func_bodynull を返したかを HAKO_SELFHOST_TRACE=1 でログ出力。
    • tools/hakorune_emit_mir.sh
      • 既存の head/tail ログ出力で [builder/selfhost-first:*] タグがそのまま表示されることを確認済み(追加改修なし)。
  • 成果物:
    • selfhost-first で Stage1 CLI を通したときに、どの関数/構造がまだ未サポートなのかがログで推測できる状態。

Step 1 — defs injection の再設計

  • Status: initial-implementedmain 必須チェックはトグル付き; multi-function への完全移行は後続 Step
  • 目的: FuncLoweringBox.inject_funcs で main 関数の有無を意識し、multi-function モジュールの土台を整える。
  • 作業:
    • inject_funcs 内で HAKO_MIR_BUILDER_REQUIRE_MAIN=1 のときに "name":"main" を含まない functions 配列を拒否し、[builder/funcs:fail:no-main] をトレース出力して injection をスキップする(既定では OFF なので既存挙動は維持)。
    • 将来フェーズで、Program.body から生成した main と defs を「2段構え」でマージする APImain 名の受け渡しなど)を追加する。
  • 成果物(現段階):
    • Env トグルを有効化した状態では「main を含まない MIR に defs だけ差し込む」ケースを検知できるようになり、Stage1 実装時に安全に stricter モードへ移行する足場ができた。

Step 2 — _lower_func_body の拡張ローカルif

  • 目的: Stage1 CLI の HakoCli.cmd_emit_program_json のような「Local / Assign / If / Return だけで構成された関数」を selfhost builder で lowering できるようにする。
  • 作業:
    • Body を JsonFrag で走査し、ローカル導入・代入・分岐を MIR ブロックへ展開する最小ロジックを FuncLoweringBox に追加。
    • Loop が出た時は [builder/funcs:unsupported:loop](仮タグ)を出して Fail-FastStep 3 で LoopForm 対応を行う)。
  • 成果物:
    • Loop を含まない defs は selfhost builder で MIR 関数にできるようになり、Stage1 CLI の emit/build ハンドラの半分程度を selfhost パスで賄える。

Step 3 — LoopLoopFormの受理

  • 目的: LoopForm 正規化済みの while/for を MIR に落とす。
  • 作業:
    • lower_loop_simple_box / lower_loop_sum_bc_box など既存 helper を FuncLoweringBox 側からも利用できるようにし、LoopForm のキャリアを MIR へ展開。
    • LoopForm の制約を満たさないケースは [builder/funcs:unsupported:loopform] で Fail-Fast。
  • 成果物:
    • cmd_build_exeloop(i < argc) 等、Stage1 CLI の代表的な while/for パターンを selfhost builder で通せる。

Step 4 — MethodCall / ExternCall パリティ

  • 目的: hostbridge.extern_invoke / FileBox / ArrayBox など Stage1 CLI で多用される呼び出しを selfhost builder でも再現。
  • 作業:
    • FuncLoweringBox に MethodCall/ExternCall の lowering ルートを追加し、mir_call Method もしくは extern_call を生成。
    • func_map により me.cmd_build_exe のような self-call も Box.method/N に resolve未対応の呼び出しは [builder/funcs:unsupported:call] で Fail
  • 成果物:
    • Stage1 CLI の主要メソッド体が selfhost builder で MIR 関数化できる。

Step 5 — call resolve / methodize 後処理

  • 目的: 生成済み MIR に対して call resolver / methodize pass をかけ、Rust provider と同じ命名・呼び出し形式を実現。
  • 作業:
    • HAKO_MIR_BUILDER_CALL_RESOLVE=1 を本格利用し、call の Const("Box.method/N")mir_call に変換。Stage1 CLI で mir_call Method を使うケースをテストし、Methodize との組み合わせでも崩れないことを確認。

Step 6 — selfhost-first canary / build_stage1.sh

  • 目的: selfhost builder を既定 ON に戻す準備。
  • 作業:
    • stage1_launcher_program_to_mir_canary_vm.sh の selfhost-first 版を追加して selfhost builder 単独で 60KB 級 MIR を生成できることを検証。
    • tools/selfhost/build_stage1.sh を selfhost-first で回し、selfhost builder 由来の MIR で emit program-json / emit mir-json / build exe が通ることを確認。
    • 問題が無ければ HAKO_SELFHOST_BUILDER_FIRST=1 を既定に戻す別PRでも可

作業順は Step 0 → 1 → 2 → 3 → 4 → 5 → 6 を想定、各ステップで必ず docsこのファイルCURRENT_TASKとスモーク/テストを更新する。***