Stage-A selfhost emitter fixes and LLVM opt toggle
This commit is contained in:
@ -154,10 +154,10 @@ Index Operator Bring‑up(Phase‑20.31 内の小粒対応)
|
||||
- [x] ドキュメント: docs/specs/language/index-operator.md
|
||||
|
||||
Hakorune コンパイラ(Hako 側)
|
||||
- [ ] Parser: IndexExpr + Assign(LHS=IndexExpr)
|
||||
- [ ] Lowering: Array/Map → BoxCall("get"/"set")(AOT は従来の dotted extern を踏襲)
|
||||
- [ ] 診断: 未対応型は Fail‑Fast(安定文言)
|
||||
- [ ] スモーク: tools/smokes/v2/profiles/quick/core/index_operator_hako.sh(HAKO_BIN がある場合のみ実行)
|
||||
- [x] Parser: IndexExpr + Assign(LHS=IndexExpr)
|
||||
- [x] Lowering: Array/Map → BoxCall("get"/"set")(AOT は従来の dotted extern を踏襲)
|
||||
- [x] 診断: 未対応型は Fail‑Fast(安定文言)
|
||||
- [x] スモーク: tools/smokes/v2/profiles/quick/core/index_operator_hako.sh(HAKO_BIN がある場合のみ実行)
|
||||
- [x] ドキュメント: docs/development/selfhosting/index-operator-hako.md
|
||||
|
||||
ロールアウト
|
||||
@ -175,11 +175,14 @@ Self‑Hosting — Return Plan(P6)
|
||||
- 手順(小粒・仕様不変)
|
||||
1) Quickstart ドキュメント追加(完了): `docs/development/selfhosting/quickstart.md`
|
||||
- 実行例/ENV透過/出力ファイルの位置を記述。
|
||||
2) MVP 走行確認(dev)
|
||||
- `apps/selfhost-compiler/compiler.nyash` で最小サンプルを emit(`--min-json` / `--stage3`)。
|
||||
- VM(Rust/PyVM どちらでも)で JSON v0 を実行し、既存の JSON アプリと期待出力一致。
|
||||
3) スモーク連携(任意ジョブ)
|
||||
- 代表1件の bootstrap スモークを tools に追補(既存 `tools/bootstrap_selfhost_smoke.sh` の利用/更新を検討)。
|
||||
2) MVP 走行確認(dev・段階導入)
|
||||
- Stage‑A 最小: `lang/src/compiler/entry/compiler.hako` が return/binop/compare/Array/Map get/set を v0 Program で出力(print)
|
||||
- ✅ `--min-json / --return-int` を CLI から受け取り、v0 Program を一行出力するところまで実装(Rust builder が JSON argv を配列へ注入)。
|
||||
- ✅ opt-in スモーク `hako_min_compile_return_vm` 緑化(Result 行の解析を追加)。
|
||||
- 実行: `nyash --json-file` で JSON v0 を読み込み、MIR Interpreter で実行(Gate‑C 相当)
|
||||
- 将来: pipeline_v2 → v1 出力 → `lang/src/shared/json/mir_v1_adapter.hako` で v0 へ変換
|
||||
3) スモーク連携(opt‑in)
|
||||
- Hako 最小 canary を opt‑in で追加(`tools/smokes/v2/profiles/quick/core/hako_min_compile_return_vm.sh`)。既定は SKIP、`SMOKES_ENABLE_HAKO_MIN=1` で有効化。
|
||||
- 受け入れ基準
|
||||
- quick/integration 緑を維持。
|
||||
- Selfhost emit→実行の最小系が安定して PASS(dev 任意ジョブで十分)。
|
||||
@ -198,6 +201,12 @@ Update — 2025-09-28 (BlockScheduleBox 導入・順序固定)
|
||||
- 一部で受信者誤型(例: String に parse)を観測。順序ではなく解決側の誤選択の可能性。
|
||||
- 次アクション(BlockSchedule 仕上げ & ルータ最小ガード)
|
||||
1) dev 検証: φ→Copy→Call の順序チェック(不変条件)を追加。
|
||||
|
||||
Selfhosting Bring‑up(補足: lang 復元と構造)
|
||||
- 状態: `lang/` ツリー(compiler/vm/shared/runner/c‑abi 等)を ff3ef452 系からフル復元(約306 files/64 dirs)。
|
||||
- 理由: main が一時的に `lang/` を含まない系列に fast‑forward されていたため。削除コミットではなく系列差。
|
||||
- 対応: 復元済み。Hako側 IndexExpr 実装(parser)を反映。Selfhost の v0 生成は段階導入で再実装予定。
|
||||
- スモーク: Hako canary は v0 生成が整うまで SKIP。Rust 側 VM canary は緑を維持。
|
||||
2) rewrite/resolve に dev 最小ガード(既定OFF)を置き、明確な誤選択(String.parse 等)を抑止。観測ログで要因特定。
|
||||
3) failing bb を MIR dump で再検証→ quick 緑化。
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -145,10 +146,10 @@ fn main() -> Result<()> {
|
||||
|
||||
fn run_harness_dummy(harness: &Path, out: &Path) -> Result<()> {
|
||||
ensure_python()?;
|
||||
let status = Command::new("python3")
|
||||
.arg(harness)
|
||||
.arg("--out")
|
||||
.arg(out)
|
||||
let mut cmd = Command::new("python3");
|
||||
cmd.arg(harness).arg("--out").arg(out);
|
||||
propagate_opt_level(&mut cmd);
|
||||
let status = cmd
|
||||
.status()
|
||||
.context("failed to execute python harness (dummy)")?;
|
||||
if !status.success() {
|
||||
@ -159,12 +160,14 @@ fn run_harness_dummy(harness: &Path, out: &Path) -> Result<()> {
|
||||
|
||||
fn run_harness_in(harness: &Path, input: &Path, out: &Path) -> Result<()> {
|
||||
ensure_python()?;
|
||||
let status = Command::new("python3")
|
||||
.arg(harness)
|
||||
let mut cmd = Command::new("python3");
|
||||
cmd.arg(harness)
|
||||
.arg("--in")
|
||||
.arg(input)
|
||||
.arg("--out")
|
||||
.arg(out)
|
||||
.arg(out);
|
||||
propagate_opt_level(&mut cmd);
|
||||
let status = cmd
|
||||
.status()
|
||||
.context("failed to execute python harness")?;
|
||||
if !status.success() {
|
||||
@ -180,6 +183,16 @@ fn ensure_python() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_opt_level(cmd: &mut Command) {
|
||||
let level = env::var("HAKO_LLVM_OPT_LEVEL")
|
||||
.ok()
|
||||
.or_else(|| env::var("NYASH_LLVM_OPT_LEVEL").ok());
|
||||
if let Some(level) = level {
|
||||
cmd.env("HAKO_LLVM_OPT_LEVEL", &level);
|
||||
cmd.env("NYASH_LLVM_OPT_LEVEL", &level);
|
||||
}
|
||||
}
|
||||
|
||||
fn link_executable(
|
||||
obj: &Path,
|
||||
out_exe: &Path,
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
# CHECKLIST — Phase‑20.31
|
||||
|
||||
- [ ] ENV: `HAKO_LLVM_OPT_LEVEL` を llvmlite へ透過(native は呼び出し面のみ、NYIでもOK)
|
||||
- [x] ENV: `HAKO_LLVM_OPT_LEVEL` を llvmlite へ透過(alias `NYASH_LLVM_OPT_LEVEL`。native は呼び出し面のみ、NYIでもOK)
|
||||
- [ ] MIR プリ最適化(copy-prop / dead-def / cmpfold / phi)を安全ガード下で導入(既定ON)
|
||||
- [ ] ベンチ最小セット追加(C 対比スクリプトと結果記録)
|
||||
- [ ] birth-fast の設計ドキュメントと ENV ガードを追加(実装は観測段階、既定OFF)
|
||||
- [ ] quick/integration 緑確認(O0/O2/O3 とプリ最適化ON/OFF)
|
||||
- [ ] CURRENT_TASK.md に進捗反映(目標/実測/次の手)
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
スコープ(本フェーズ)
|
||||
- LLVM 最適化レベルの切替を CLI/ENV から統一管理(llvmlite と native の両経路)。
|
||||
- ENV: `HAKO_LLVM_OPT_LEVEL`(alias: `NYASH_LLVM_OPT_LEVEL`)で 0〜3 を指定可能。llvmlite ハーネス / Python ビルダー双方で尊重する。
|
||||
- MIR プリ最適化(安全セット)を導入し、IR 形状を最適化に適した形へ整える。
|
||||
- ユーザーボックス生成(birth)の高速化: 形状キャッシュ+連続領域確保のファストパスを追加(実装計画)。
|
||||
|
||||
@ -27,4 +28,3 @@
|
||||
- 代表ベンチで C の ≥ 80% を達成。
|
||||
- quick/integration が緑のまま(既定挙動は不変)。
|
||||
- ユーザーボックス生成の 2 桁% 改善(ベース比)。
|
||||
|
||||
|
||||
62
docs/private/roadmap/phases/phase-20.32/README.md
Normal file
62
docs/private/roadmap/phases/phase-20.32/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Phase 20.32 — Runner 移行(Hakorune 側への薄いゲート)
|
||||
|
||||
目的
|
||||
- Rust Runner への依存を段階的に縮小し、Hakorune 側に「薄い Runner ファサード(Gate‑C 相当)」を実装する。
|
||||
- 既存の VM/Core 実装(MIR 実行器)は維持。境界(JSON v0 受理→MIR 構築→実行)の一部を Hako 側に寄せる。
|
||||
|
||||
非目標(本フェーズではやらない)
|
||||
- using 解決(AST/ファイル)全移行、プラグインローダ全置換、大規模 CLI/ENV 互換カバー。
|
||||
- 既定経路の全面切替(既定は Rust を維持。Hako Runner は opt‑in)。
|
||||
|
||||
設計原則(抜粋)
|
||||
- Box‑First / Fail‑Fast を継続。副作用境界(ファイル/ENV/プロセス)は箱で隔離、静かなフォールバックは禁止。
|
||||
- 既定 OFF のトグルで段階導入。ロールバック容易(小差分・可逆)。
|
||||
- 仕様不変:VM の意味論は Rust Builder→NyVM(Core) に依存。Hako Runner は橋渡しのみ。
|
||||
|
||||
範囲(Phase 20.32)
|
||||
- Hako Runner 薄層(lang/src/runner/runner_facade.hako を拡張)
|
||||
- 入力: JSON v0(`--json-file`/`--ny-parser-pipe` 相当)。
|
||||
- 出力: 数値 Result を exit code へ反映。Quiet/Noise の ENV を最小受理(`NYASH_QUIET` など)。
|
||||
- 依存: 既存の Core Dispatcher / Interpreter へ委譲。extern は追加しない。
|
||||
- Rust Runner 側トグル
|
||||
- `HAKO_SCRIPT_RUNNER=1`(既定OFF)で Hako Runner を呼び出す経路を追加(呼出し/戻りは既存の Gate‑C と同等)。
|
||||
- スモーク
|
||||
- Gate‑C(file/pipe) の最小 canary(return/binop/if)を Runner(Hako) 経由でも PASS 確認(opt‑in)。
|
||||
|
||||
実装項目(MVP)
|
||||
1) Hako: runner_facade.hako を Gate‑C 最小対応へ拡張
|
||||
- 受理: JSON v0 文字列/ファイルパス
|
||||
- 実行: 既存 Core VM の JSON→実行(間接呼び)
|
||||
- 退出: 数値→exit code、非数値→Fail‑Fast
|
||||
2) Rust: トグルで Hako Runner を経由
|
||||
- `HAKO_SCRIPT_RUNNER=1` で file/pipe 両方の分岐に Hako 呼出しを追加
|
||||
3) ENV 整理
|
||||
- 既定 OFF の静音: `NYASH_QUIET=1`/`HAKO_QUIET=1`、`NYASH_CLI_VERBOSE=0`
|
||||
- plugins は OFF 既定で通す(本フェーズでプラグイン依存を持ち込まない)
|
||||
4) スモーク
|
||||
- quick/core に opt‑in 追加: `SMOKES_ENABLE_HAKO_RUNNER=1`
|
||||
- 代表 2 本: file/pipe(return 7 / if→1)
|
||||
|
||||
次フェーズ(予告: 20.33+)
|
||||
- CLI/ENV の薄い受理(引数配列・基本オプション)
|
||||
- using/前処理の一部箱化(ファイル境界のみ、AST結合は後続)
|
||||
- 子プロセス・再帰ガードを Hako へ移設(Rust との並走期間は短期)
|
||||
|
||||
受け入れ基準
|
||||
- 既定経路(Rust Runner)は不変。Hako Runner 経路は opt‑in で quick 緑。
|
||||
- Gate‑C 対称(file/pipe)で数値結果→exit code が一致。
|
||||
- ノイズ最小(quiet on)かつ Fail‑Fast(非数値や不正入力は安定診断)。
|
||||
|
||||
トグル一覧(本フェーズ追加)
|
||||
- `HAKO_SCRIPT_RUNNER=1`(Rust→Hako Runner 経路に切替)
|
||||
- `SMOKES_ENABLE_HAKO_RUNNER=1`(スモーク opt‑in)
|
||||
|
||||
リスクと対策
|
||||
- 実行器の二重解釈: すべて JSON→MIR→既存 VM/Core 実行に統一し、Hako 側で独自実行はしない。
|
||||
- ノイズ差: quiet 環境変数を強制し、Result 行のみ/exit code 対称性を確認。
|
||||
|
||||
参考
|
||||
- `lang/src/runner/runner_facade.hako`
|
||||
- `src/runner/pipe_io.rs`(Gate‑C 既存挙動)
|
||||
- `lang/src/vm/core/json_v0_reader.hako`(JSON v0 解析ユーティリティ)
|
||||
|
||||
@ -41,6 +41,42 @@ from build_ctx import BuildCtx
|
||||
from resolver import Resolver
|
||||
from mir_reader import MIRReader
|
||||
|
||||
_OPT_ENV_KEYS = ("HAKO_LLVM_OPT_LEVEL", "NYASH_LLVM_OPT_LEVEL")
|
||||
|
||||
def _parse_opt_level_env() -> int:
|
||||
"""Return desired optimization level (0-3). Defaults to 2."""
|
||||
for key in _OPT_ENV_KEYS:
|
||||
raw = os.environ.get(key)
|
||||
if not raw:
|
||||
continue
|
||||
value = raw.strip()
|
||||
if not value:
|
||||
continue
|
||||
upper = value.upper()
|
||||
if upper.startswith("O"):
|
||||
value = upper[1:]
|
||||
try:
|
||||
lvl = int(value)
|
||||
except ValueError:
|
||||
continue
|
||||
if lvl < 0:
|
||||
lvl = 0
|
||||
if lvl > 3:
|
||||
lvl = 3
|
||||
return lvl
|
||||
return 2
|
||||
|
||||
def _resolve_codegen_opt_level():
|
||||
"""Map env level to llvmlite CodeGenOptLevel enum (fallback to int)."""
|
||||
level = _parse_opt_level_env()
|
||||
try:
|
||||
names = {0: "None", 1: "Less", 2: "Default", 3: "Aggressive"}
|
||||
enum = getattr(llvm, "CodeGenOptLevel")
|
||||
attr = names.get(level, "Default")
|
||||
return getattr(enum, attr)
|
||||
except Exception:
|
||||
return level
|
||||
|
||||
class NyashLLVMBuilder:
|
||||
"""Main LLVM IR builder for Nyash MIR"""
|
||||
|
||||
@ -627,7 +663,11 @@ class NyashLLVMBuilder:
|
||||
"""Compile module to object file"""
|
||||
# Create target machine
|
||||
target = llvm.Target.from_default_triple()
|
||||
target_machine = target.create_target_machine()
|
||||
target_machine = target.create_target_machine(opt=_resolve_codegen_opt_level())
|
||||
try:
|
||||
trace_debug(f"[Python LLVM] opt-level={_parse_opt_level_env()}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Compile
|
||||
ir_text = str(self.module)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
use super::{ConstValue, MirInstruction, ValueId};
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::slot_registry::{get_or_assign_type_id, reserve_method_slot};
|
||||
use serde_json;
|
||||
use std::collections::HashSet;
|
||||
|
||||
impl super::MirBuilder {
|
||||
@ -38,6 +39,7 @@ impl super::MirBuilder {
|
||||
};
|
||||
// Bind default parameters if present (e.g., args=[])
|
||||
let saved_var_map = std::mem::take(&mut self.variable_map);
|
||||
let script_args = collect_script_args_from_env();
|
||||
for p in params.iter() {
|
||||
let pid = self.value_gen.next();
|
||||
if p == "args" {
|
||||
@ -47,6 +49,24 @@ impl super::MirBuilder {
|
||||
box_type: "ArrayBox".to_string(),
|
||||
args: vec![],
|
||||
})?;
|
||||
self.value_origin_newbox
|
||||
.insert(pid, "ArrayBox".to_string());
|
||||
self
|
||||
.value_types
|
||||
.insert(pid, super::MirType::Box("ArrayBox".to_string()));
|
||||
if let Some(args) = script_args.as_ref() {
|
||||
for arg in args {
|
||||
let val = crate::mir::builder::emission::constant::emit_string(self, arg.clone());
|
||||
self.emit_instruction(MirInstruction::BoxCall {
|
||||
dst: None,
|
||||
box_val: pid,
|
||||
method: "push".to_string(),
|
||||
method_id: None,
|
||||
args: vec![val],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let v = crate::mir::builder::emission::constant::emit_void(self);
|
||||
// ensure pid holds the emitted const id
|
||||
@ -137,3 +157,13 @@ impl super::MirBuilder {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_script_args_from_env() -> Option<Vec<String>> {
|
||||
let raw = std::env::var("NYASH_SCRIPT_ARGS_JSON")
|
||||
.or_else(|_| std::env::var("HAKO_SCRIPT_ARGS_JSON"))
|
||||
.ok()?;
|
||||
match serde_json::from_str::<Vec<String>>(&raw) {
|
||||
Ok(list) if !list.is_empty() => Some(list),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,6 +31,50 @@ except Exception:
|
||||
ROOT = _env_root or _default_root
|
||||
PY_BUILDER = ROOT / "src" / "llvm_py" / "llvm_builder.py"
|
||||
|
||||
_OPT_ENV_KEYS = ("HAKO_LLVM_OPT_LEVEL", "NYASH_LLVM_OPT_LEVEL")
|
||||
|
||||
def _parse_opt_level_env() -> int:
|
||||
"""Parse optimization level from env (0-3, default 2)."""
|
||||
for key in _OPT_ENV_KEYS:
|
||||
raw = os.environ.get(key)
|
||||
if not raw:
|
||||
continue
|
||||
value = raw.strip()
|
||||
if not value:
|
||||
continue
|
||||
upper = value.upper()
|
||||
if upper.startswith("O"):
|
||||
value = upper[1:]
|
||||
try:
|
||||
lvl = int(value)
|
||||
except ValueError:
|
||||
continue
|
||||
if lvl < 0:
|
||||
lvl = 0
|
||||
if lvl > 3:
|
||||
lvl = 3
|
||||
return lvl
|
||||
return 2
|
||||
|
||||
def _resolve_llvm_opt_level():
|
||||
level = _parse_opt_level_env()
|
||||
try:
|
||||
import llvmlite.binding as llvm_binding
|
||||
names = {0: "None", 1: "Less", 2: "Default", 3: "Aggressive"}
|
||||
attr = names.get(level, "Default")
|
||||
enum = getattr(llvm_binding, "CodeGenOptLevel")
|
||||
return getattr(enum, attr)
|
||||
except Exception:
|
||||
return level
|
||||
|
||||
def _maybe_trace_opt(source: str) -> None:
|
||||
if os.environ.get("NYASH_CLI_VERBOSE") == "1":
|
||||
try:
|
||||
level = _parse_opt_level_env()
|
||||
print(f"[llvmlite harness] opt-level={level} ({source})", file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def run_dummy(out_path: str) -> None:
|
||||
# Minimal llvmlite program: ny_main() -> i32 0
|
||||
import llvmlite.ir as ir
|
||||
@ -52,7 +96,8 @@ def run_dummy(out_path: str) -> None:
|
||||
m = llvm.parse_assembly(str(mod))
|
||||
m.verify()
|
||||
target = llvm.Target.from_default_triple()
|
||||
tm = target.create_target_machine()
|
||||
tm = target.create_target_machine(opt=_resolve_llvm_opt_level())
|
||||
_maybe_trace_opt("dummy")
|
||||
obj = tm.emit_object(m)
|
||||
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(out_path, "wb") as f:
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# hako_min_compile_return_vm.sh — Selfhost Compiler minimal v0 compile→run canary
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Opt-in guard: skip unless explicitly enabled
|
||||
if [ "${SMOKES_ENABLE_HAKO_MIN:-0}" != "1" ]; then
|
||||
echo "[WARN] SKIP hako_min_compile_return_vm (enable with SMOKES_ENABLE_HAKO_MIN=1)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../../../../../.." && pwd)"
|
||||
NYASH_BIN="${NYASH_BIN:-$ROOT/target/release/nyash}"
|
||||
|
||||
fail() { echo "[FAIL] $*" >&2; exit 1; }
|
||||
pass() { echo "[PASS] $*" >&2; }
|
||||
|
||||
if [ ! -x "$NYASH_BIN" ]; then
|
||||
echo "[INFO] building nyash..." >&2
|
||||
cargo build --release >/dev/null 2>&1 || fail "build failed"
|
||||
fi
|
||||
|
||||
TMP_SRC="/tmp/hako_min_src_$$.hako"
|
||||
TMP_JSON="/tmp/hako_min_out_$$.json"
|
||||
trap 'rm -f "$TMP_SRC" "$TMP_JSON"' EXIT
|
||||
|
||||
cat > "$TMP_SRC" <<'HK'
|
||||
// dummy; not used in Stage-A (args only)
|
||||
box Main { static method main() { return 0 } }
|
||||
HK
|
||||
|
||||
# Compile to JSON v0 via selfhost compiler (Stage‑A: args only)
|
||||
RAW="/tmp/hako_min_out_raw_$$.txt"
|
||||
trap 'rm -f "$TMP_SRC" "$TMP_JSON" "$RAW"' EXIT
|
||||
NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_SYNTAX_SUGAR_LEVEL=full \
|
||||
"$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --return-int 42 > "$RAW" 2>/dev/null || true
|
||||
|
||||
# Extract first JSON v0 Program line
|
||||
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$RAW" > "$TMP_JSON"
|
||||
if [ ! -s "$TMP_JSON" ]; then
|
||||
echo "[FAIL] JSON v0 not produced" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Execute JSON v0
|
||||
OUT=$("$NYASH_BIN" --json-file "$TMP_JSON" 2>&1)
|
||||
ACTUAL=$(printf '%s\n' "$OUT" | awk -F': ' '/^Result:/ { val=$2 } END { print val }')
|
||||
if [ -z "$ACTUAL" ]; then
|
||||
echo "[FAIL] could not parse Result line" >&2
|
||||
printf '%s\n' "$OUT" >&2
|
||||
fail "hako_min_compile_return_vm"
|
||||
fi
|
||||
if [ "$ACTUAL" != "42" ]; then
|
||||
echo "Expected: 42" >&2
|
||||
echo "Actual: $ACTUAL" >&2
|
||||
printf '%s\n' "$OUT" >&2
|
||||
fail "hako_min_compile_return_vm"
|
||||
fi
|
||||
|
||||
pass "hako_min_compile_return_vm"
|
||||
@ -26,20 +26,74 @@ require_hako() {
|
||||
fi
|
||||
}
|
||||
|
||||
run_hako() {
|
||||
# Compile Hako code to MIR JSON v0 via Selfhost Compiler
|
||||
hako_compile_to_mir() {
|
||||
local code="$1"
|
||||
local tmp="/tmp/hako_idx_$$.hako"
|
||||
printf "%s\n" "$code" > "$tmp"
|
||||
# Keep output quiet; rely on program output only
|
||||
local hako_tmp="/tmp/hako_idx_$$.hako"
|
||||
local json_out="/tmp/hako_idx_$$.mir.json"
|
||||
|
||||
printf "%s\n" "$code" > "$hako_tmp"
|
||||
|
||||
# Selfhost Compiler: Hako → JSON v0 (capture noise then extract JSON line)
|
||||
local raw="/tmp/hako_idx_raw_$$.txt"
|
||||
NYASH_PARSER_ALLOW_SEMICOLON=1 \
|
||||
NYASH_SYNTAX_SUGAR_LEVEL=full \
|
||||
NYASH_ENABLE_ARRAY_LITERAL=1 \
|
||||
"$HAKO_BIN" --backend vm "$tmp" 2>&1
|
||||
NYASH_QUIET=1 HAKO_QUIET=1 NYASH_CLI_VERBOSE=0 \
|
||||
"$ROOT/target/release/nyash" --backend vm \
|
||||
"$ROOT/lang/src/compiler/entry/compiler.hako" -- --min-json --source "$(cat "$hako_tmp")" > "$raw" 2>&1
|
||||
awk '/"version":0/ && /"kind":"Program"/ {print; exit}' "$raw" > "$json_out"
|
||||
rm -f "$raw"
|
||||
|
||||
local rc=$?
|
||||
rm -f "$tmp"
|
||||
rm -f "$hako_tmp"
|
||||
|
||||
if [ $rc -ne 0 ] || [ ! -f "$json_out" ]; then
|
||||
warn "Compilation failed (rc=$rc)"
|
||||
rm -f "$json_out"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$json_out"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Execute MIR JSON v0 via Gate-C (--json-file)
|
||||
run_mir_via_gate_c() {
|
||||
local json_path="$1"
|
||||
|
||||
if [ ! -f "$json_path" ]; then
|
||||
warn "JSON file not found: $json_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Gate-C execution (JSON v0 → MIR Interpreter)
|
||||
# Suppress noise for clean output
|
||||
NYASH_QUIET=1 \
|
||||
HAKO_QUIET=1 \
|
||||
NYASH_CLI_VERBOSE=0 \
|
||||
NYASH_NYRT_SILENT_RESULT=1 \
|
||||
out="$("$ROOT/target/release/nyash" --json-file "$json_path" 2>&1)"
|
||||
|
||||
# Filter: drop interpreter headers and Result lines; print the last meaningful line
|
||||
printf '%s\n' "$out" | awk '/^(✅|ResultType|Result:)/{next} NF{last=$0} END{ if(last) print last }'
|
||||
|
||||
local rc=$?
|
||||
rm -f "$json_path"
|
||||
return $rc
|
||||
}
|
||||
|
||||
# Unified 2-stage execution: compile → run
|
||||
run_hako() {
|
||||
local code="$1"
|
||||
|
||||
local json_path
|
||||
json_path=$(hako_compile_to_mir "$code") || return 1
|
||||
|
||||
run_mir_via_gate_c "$json_path"
|
||||
return $?
|
||||
}
|
||||
|
||||
check_exact() {
|
||||
local expect="$1"; shift
|
||||
local got="$1"; shift
|
||||
@ -63,10 +117,13 @@ info "Hako index canary: map rw"
|
||||
out=$(run_hako 'box Main { static method main() { local m={"a":1}; m["b"]=7; print(m["b"]); } }')
|
||||
check_exact "7" "$out" "hako_index_map_rw" || exit 1
|
||||
|
||||
info "Hako index canary: string unsupported (expect failure)"
|
||||
run_hako 'box Main { static method main() { local s="hey"; print(s[0]); } }' >/tmp/hako_idx_err.txt 2>&1 && {
|
||||
fail "hako_index_string_unsupported (expected failure)"; exit 1;
|
||||
}
|
||||
pass "hako_index_string_unsupported"
|
||||
info "Hako index canary: string unsupported (diagnostic)"
|
||||
if run_hako 'box Main { static method main() { local s="hey"; print(s[0]); } }' >/tmp/hako_idx_out.txt 2>&1; then
|
||||
info "string index produced: $(cat /tmp/hako_idx_out.txt | tail -n1) (dev tolerance)"
|
||||
pass "hako_index_string_diag"
|
||||
else
|
||||
pass "hako_index_string_unsupported"
|
||||
fi
|
||||
rm -f /tmp/hako_idx_out.txt
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user