Phase 25.1a: selfhost builder hotfix (fn rename, docs)

This commit is contained in:
nyash-codex
2025-11-15 05:42:32 +09:00
parent 8d9bbc40bd
commit 6856922374
40 changed files with 2013 additions and 72 deletions

View File

@ -1,28 +1,44 @@
# Runner Facade (ScriptFirst) — Phase 20.10
# Runner Facade / Stage1 CLI — Runner Layer Guide
Responsibility
- Provide a thin, optin runner facade in Hakorune (script) to orchestrate entry selection and pre/post hooks.
- Delegates actual execution to existing backends (Rust VM / LLVM). No behavior change by default.
## Responsibility
- Provide script-side orchestration primitives for execution:
- Runner facade (`runner_facade.hako`) for entry selection and pre/post hooks.
- Stage1 CLI launcher (`launcher.hako`) for top-level command dispatch.
- Delegate actual execution to existing backendsRust VM / LLVM / ny-llvmc。既定挙動は変えない。
Gate
- Enable with `HAKO_SCRIPT_RUNNER=1` (default OFF). In 20.10 this runner is not wired by default; wiring will be added behind the gate.
## Files
Contracts (draft)
- Entry: `Runner.run(entry: string, args: array<string>) -> i64`
- Prehooks: validate entry, shape args, emit diagnostics (short tokens) on failure.
- Posthooks: normalize result (e.g., quiet result mode), optional metrics/logging.
- `runner_facade.hako`
- Contractdraft:
- Entry: `Runner.run(entry: string, args: array<string>) -> i64`
- Gate: `HAKO_SCRIPT_RUNNER=1`default OFF
- Role:
- Script-first runner facadePhase 20.10)。
- Pre-hooks: validate entry/args, emit short diagnostics。
- Post-hooks: normalize result / metrics将来
- Notes:
- Keep this layer pure; platform I/O は C-ABI 側に委譲。
- Fail-Fast: invalid entry/args は非0で即終了。
- Short diagnostics:
- Success: `[script-runner] invoke`
- Failure: `[script-runner] invoke: FAIL`
Notes
- Keep this layer pure and free of platform I/O. Defer I/O to CABI utilities (`hako_*`).
- FailFast: invalid entry/args → emit short diagnostics and return nonzero.
- BoxFirst: add adapters for boundary objects (args, env) instead of sprinkling conditions.
- `launcher.hako`
- Contractdraft:
- Entry: `Main.main(args: array<string>) -> i64`
- Role: Stage1 hakorune CLI のトップレベル dispatcher。
- コマンド: `run` / `build` / `emit` / `check`(詳細は docs/development/runtime/cli-hakorune-stage1.md
- Current statusPhase 25.1:
- 構造のみ実装(`HakoCli` box にコマンド別のメソッドを定義)。
- 各コマンドはまだプレースホルダで、`"[hakorune] <cmd>: not implemented yet"` を出力して終了コード 9093 を返す。
- 実際のパイプラインStageB / MirBuilder / AotPrep / ny-llvmc など)への接続は後続フェーズで段階的に実装する。
- Design reference:
- `docs/development/runtime/cli-hakorune-stage1.md` を Stage1 CLI の仕様 SSOT として参照すること。
Short Diagnostics & Observability
- Preinvoke emits stable oneliners for smokes:
- Success: `[script-runner] invoke` (stdout)
- Failure (dev injection or panic): `[script-runner] invoke: FAIL` (stdout)
- The runner wiring prints a gate trace to stderr: `[script-runner] gate=on (facade)`.
Devonly toggle (TTL: Phase 20.10 bringup)
- `HAKO_SCRIPT_RUNNER_FORCE_FAIL=1` forces the preinvoke to emit `[script-runner] invoke: FAIL` without executing the facade program.
- Scope: tests/smokes only. Remove after runner wiring stabilizes (documented here as a temporary aid).
## Notes
- Runner 層は「構造とオーケストレーション専用レイヤ」として扱う。
- 言語意味論・最適化ロジックは compiler / opt / AotPrep に留める。
- VM/LLVM の実行コアは Rust 側Stage0 / NyRTに委譲する。
- Fail-Fast 原則:
- 未実装コマンドや不正な引数は明示的なメッセージ非0終了コードで返す。
- 暗黙のフォールバックや静かな無視は行わない。

View File

@ -1,9 +1,441 @@
// launcher.hako — Minimal selfhost launcher (AOT target)
// Goal: produce lang/bin/hakorune via tools/build_llvm.sh
// launcher.hako — Stage1 Hakorune CLI launcher (draft)
//
// Responsibility
// - Provide a thin, script-side CLI entrypoint for Stage1 hakorune.
// - Parse top-level command/subcommand and delegate to dedicated handlers.
// - Keep implementation fail-fast and minimal; real work is delegated to
// runner / compiler / AOT helpers and ultimately Stage0 (Rust) services.
//
// Notes
// - CLI surface is specified in docs/development/runtime/cli-hakorune-stage1.md.
// - This file only defines the structural dispatcher; individual commands
// are implemented incrementally in later phases.
static box Main {
// No-args main() to avoid runtime Array allocation in AOT
main(){
return 0;
using lang.mir.builder.MirBuilderBox
using lang.compiler.build.build_box as BuildBox
static box HakoCli {
// Entry from Main.main(args)
method run(args){
@argc = 0
if args { argc = args.size() }
if argc == 0 {
// No command: print minimal usage and fail-fast
print("[hakorune] usage: hakorune <command> [options]")
return 1
}
@cmd_raw = args.get(0)
@cmd = "" + cmd_raw
if cmd == "run" {
return self.cmd_run(args)
} else if cmd == "build" {
return self.cmd_build(args)
} else if cmd == "emit" {
return self.cmd_emit(args)
} else if cmd == "check" {
return self.cmd_check(args)
}
print("[hakorune] unknown command: " + cmd)
return 2
}
// hakorune run [options] <entry.hako> [-- script_args...]
// - See docs/development/runtime/cli-hakorune-stage1.md for semantics.
// - Phase 25.1: placeholder only構造だけ定義し、後続フェーズで実装
method cmd_run(args){
print("[hakorune] run: not implemented yet")
return 90
}
// hakorune build exe [options] <entry.hako>
// - High-level wrapper around selfhost_exe_stageb / ny_mir_builder.
method cmd_build(args){
@argc = 0
if args { argc = args.size() }
if argc < 2 {
print("[hakorune] build: usage: hakorune build exe <source.hako>")
return 91
}
@sub_raw = args.get(1)
@sub = "" + sub_raw
if sub == "exe" {
return self.cmd_build_exe(args)
}
print("[hakorune] build: unknown subcommand: " + sub)
return 91
}
// hakorune build exe [-o <out>] [--quiet] <source.hako>
// - Pipeline: .hako → Program(JSON v0) → MIR(JSON) → env.codegen.emit_object/link_object → EXE
// - Requires env.codegen C-API route to be有効化されていることNYASH_LLVM_USE_CAPI=1, HAKO_V1_EXTERN_PROVIDER_C_ABI=1 等)。
method cmd_build_exe(args){
@argc = 0
if args { argc = args.size() }
if argc < 3 {
print("[hakorune] build exe: usage: hakorune build exe [-o <out>] [--quiet] <source.hako>")
return 91
}
@out_path = null
@quiet = 0
@source_path = null
@i = 2
while i < argc {
@arg_raw = args.get(i)
@arg = "" + arg_raw
if arg == "-o" || arg == "--out" {
if i + 1 >= argc {
print("[hakorune] build exe: -o/--out requires a path")
return 91
}
@op_raw = args.get(i + 1)
out_path = "" + op_raw
i = i + 2
} else if arg == "--quiet" {
quiet = 1
i = i + 1
} else {
if source_path == null {
source_path = arg
i = i + 1
} else {
print("[hakorune] build exe: unexpected extra argument: " + arg)
return 91
}
}
}
if source_path == null || source_path == "" {
print("[hakorune] build exe: source path is required")
return 91
}
// Read Hako source
@fb = new FileBox()
@ok = fb.open(source_path, "r")
if ok != 1 {
print("[hakorune] build exe: failed to open source " + source_path)
return 91
}
@src = fb.read()
fb.close()
if src == null || src == "" {
print("[hakorune] build exe: source is empty")
return 91
}
// .hako → Program(JSON v0)
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null {
print("[hakorune] build exe: BuildBox returned null")
return 91
}
@ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] build exe: unexpected Program(JSON) output (missing version/kind)")
return 91
}
// Program(JSON v0) → MIR(JSON)
@mir = MirBuilderBox.emit_from_program_json_v0(ps, null)
if mir == null {
print("[hakorune] build exe: MirBuilderBox returned null")
return 91
}
@ms = "" + mir
// MIR(JSON) → object via env.codegen.emit_object
@emit_args = new ArrayBox()
emit_args.push(ms)
@obj = hostbridge.extern_invoke("env.codegen", "emit_object", emit_args)
if obj == null || ("" + obj) == "" {
print("[hakorune] build exe: env.codegen.emit_object failed")
return 91
}
@obj_path = "" + obj
// object → EXE via env.codegen.link_object
@link_args = new ArrayBox()
link_args.push(obj_path)
if out_path != null && out_path != "" {
link_args.push(out_path)
}
@exe = hostbridge.extern_invoke("env.codegen", "link_object", link_args)
if exe == null || ("" + exe) == "" {
print("[hakorune] build exe: env.codegen.link_object failed")
return 91
}
@exe_path = "" + exe
if quiet != 1 {
print("[hakorune] build exe: " + exe_path)
}
return 0
}
// hakorune emit {program-json|mir-json} ...
// - Surface for Stage-B / MirBuilder pipeline.
method cmd_emit(args){
@argc = 0
if args { argc = args.size() }
if argc < 2 {
print("[hakorune] emit: usage: hakorune emit mir-json --from-program-json <file>")
return 92
}
@sub_raw = args.get(1)
@sub = "" + sub_raw
if sub == "mir-json" {
return self.cmd_emit_mir_json(args)
} else if sub == "program-json" {
return self.cmd_emit_program_json(args)
}
print("[hakorune] emit: unknown subcommand: " + sub)
return 92
}
// hakorune emit program-json <source.hako>
// - Compile Hako source to Program(JSON v0) using BuildBox.emit_program_json_v0.
// - Output is written to stdout on success.
method cmd_emit_program_json(args){
@argc = 0
if args { argc = args.size() }
if argc < 3 {
print("[hakorune] emit program-json: usage: hakorune emit program-json [-o <out>] [--quiet] <source.hako>")
return 92
}
@path = null
@out_path = null
@quiet = 0
@i = 2
while i < argc {
@arg_raw = args.get(i)
@arg = "" + arg_raw
if arg == "-o" || arg == "--out" {
if i + 1 >= argc {
print("[hakorune] emit program-json: -o/--out requires a path")
return 92
}
@op_raw = args.get(i + 1)
out_path = "" + op_raw
i = i + 2
} else if arg == "--quiet" {
quiet = 1
i = i + 1
} else {
if path == null {
path = arg
i = i + 1
} else {
print("[hakorune] emit program-json: unexpected extra argument: " + arg)
return 92
}
}
}
if path == null || path == "" {
print("[hakorune] emit program-json: source path is required")
return 92
}
// Read Hako source from file
@fb = new FileBox()
@ok = fb.open(path, "r")
if ok != 1 {
print("[hakorune] emit program-json: failed to open " + path)
return 92
}
@src = fb.read()
fb.close()
if src == null || src == "" {
print("[hakorune] emit program-json: source is empty")
return 92
}
// Compile to Program(JSON v0) via BuildBox
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null {
print("[hakorune] emit program-json: BuildBox returned null")
return 92
}
// Minimal validation: require Program(version 0)
@ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] emit program-json: unexpected output (missing version/kind)")
return 92
}
if out_path != null && out_path != "" {
@out_fb = new FileBox()
@ok2 = out_fb.open(out_path, "w")
if ok2 != 1 {
print("[hakorune] emit program-json: failed to open output " + out_path)
return 92
}
out_fb.write(ps)
out_fb.close()
if quiet != 1 {
print("[hakorune] emit program-json: written " + out_path)
}
} else {
print(ps)
}
return 0
}
// hakorune emit mir-json [--from-program-json <program.json>] [-o <out>] [--quiet] [<source.hako>]
// - Program(JSON v0) から MIR(JSON) を生成する経路に加えて、
// .hako ソースから直接 Program(JSON v0)→MIR(JSON) まで進める経路もサポートする。
method cmd_emit_mir_json(args){
@argc = 0
if args { argc = args.size() }
@prog_path = null
@source_path = null
@out_path = null
@quiet = 0
@i = 2
while i < argc {
@arg_raw = args.get(i)
@arg = "" + arg_raw
if arg == "--from-program-json" {
if i + 1 >= argc {
print("[hakorune] emit mir-json: --from-program-json requires a path")
return 92
}
@p_raw = args.get(i + 1)
prog_path = "" + p_raw
i = i + 2
} else if arg == "-o" || arg == "--out" {
if i + 1 >= argc {
print("[hakorune] emit mir-json: -o/--out requires a path")
return 92
}
@op_raw = args.get(i + 1)
out_path = "" + op_raw
i = i + 2
} else if arg == "--quiet" {
quiet = 1
i = i + 1
} else {
// Interpret as <source.hako> when not already set
if source_path == null {
source_path = arg
i = i + 1
} else {
print("[hakorune] emit mir-json: unknown or unsupported flag: " + arg)
return 92
}
}
}
// Prevent ambiguous usage
if prog_path != null && prog_path != "" && source_path != null && source_path != "" {
print("[hakorune] emit mir-json: specify either --from-program-json or <source.hako>, not both")
return 92
}
@prog_json = null
if prog_path != null && prog_path != "" {
// Read Program(JSON v0) from file
@fb = new FileBox()
@ok = fb.open(prog_path, "r")
if ok != 1 {
print("[hakorune] emit mir-json: failed to open " + prog_path)
return 92
}
@p = fb.read()
fb.close()
if p == null || p == "" {
print("[hakorune] emit mir-json: program JSON is empty")
return 92
}
prog_json = p
} else {
// Expect .hako source path
if source_path == null || source_path == "" {
print("[hakorune] emit mir-json: require --from-program-json <file> or <source.hako>")
return 92
}
@fb2 = new FileBox()
@ok2 = fb2.open(source_path, "r")
if ok2 != 1 {
print("[hakorune] emit mir-json: failed to open source " + source_path)
return 92
}
@src = fb2.read()
fb2.close()
if src == null || src == "" {
print("[hakorune] emit mir-json: source is empty")
return 92
}
// Compile to Program(JSON v0) via BuildBox
@prog = BuildBox.emit_program_json_v0(src, null)
if prog == null {
print("[hakorune] emit mir-json: BuildBox returned null")
return 92
}
@ps = "" + prog
if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 {
print("[hakorune] emit mir-json: unexpected Program(JSON) output (missing version/kind)")
return 92
}
prog_json = ps
}
// Convert Program(JSON v0) → MIR(JSON) via MirBuilderBox
@mir = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
if mir == null {
print("[hakorune] emit mir-json: MirBuilderBox returned null")
return 92
}
// Success: emit MIR(JSON) to stdout or file
@ms = "" + mir
if out_path != null && out_path != "" {
@out_fb = new FileBox()
@ok3 = out_fb.open(out_path, "w")
if ok3 != 1 {
print("[hakorune] emit mir-json: failed to open output " + out_path)
return 92
}
out_fb.write(ms)
out_fb.close()
if quiet != 1 {
print("[hakorune] emit mir-json: written " + out_path)
}
} else {
print(ms)
}
return 0
}
// hakorune check <entry.hako>
// - Reserved for future static checks (syntax/using/subset).
method cmd_check(args){
print("[hakorune] check: not implemented yet")
return 93
}
}
static box Main {
// Main entrypoint for Stage1 hakorune CLI.
// args: raw argv array (excluding executable name).
main(args){
@cli = new HakoCli()
return cli.run(args)
}
}