Files
hakorune/tools/smokes/v2/lib/test_runner.sh
nyash-codex dda65b94b7 Phase 21.7 normalization: optimization pre-work + bench harness expansion
- Add opt-in optimizations (defaults OFF)
  - Ret purity verifier: NYASH_VERIFY_RET_PURITY=1
  - strlen FAST enhancement for const handles
  - FAST_INT gate for same-BB SSA optimization
  - length cache for string literals in llvmlite
- Expand bench harness (tools/perf/microbench.sh)
  - Add branch/call/stringchain/arraymap/chip8/kilo cases
  - Auto-calculate ratio vs C reference
  - Document in benchmarks/README.md
- Compiler health improvements
  - Unify PHI insertion to insert_phi_at_head()
  - Add NYASH_LLVM_SKIP_BUILD=1 for build reuse
- Runtime & safety enhancements
  - Clarify Rust/Hako ownership boundaries
  - Strengthen receiver localization (LocalSSA/pin/after-PHIs)
  - Stop excessive PluginInvoke→BoxCall rewrites
- Update CURRENT_TASK.md, docs, and canaries

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 16:40:58 +09:00

881 lines
38 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# test_runner.sh - 中核実行器(強制使用)
# スモークテストv2の核心ライブラリ
# set -eは使わない個々のテストが失敗しても全体を続行するため
set -uo pipefail
# ルート/バイナリ検出CWDに依存しない実行を保証
if [ -z "${NYASH_ROOT:-}" ]; then
export NYASH_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
fi
# Prefer hakorune binary if exists; fallback to nyash for compatibility
if [ -z "${NYASH_BIN:-}" ]; then
if [ -x "$NYASH_ROOT/target/release/hakorune" ]; then
export NYASH_BIN="$NYASH_ROOT/target/release/hakorune"
else
export NYASH_BIN="$NYASH_ROOT/target/release/nyash"
fi
fi
# Debug convenience: HAKO_DEBUG=1 enables execution trace and log passthrough
if [ "${HAKO_DEBUG:-0}" = "1" ]; then
export HAKO_TRACE_EXECUTION=1
export HAKO_VERIFY_SHOW_LOGS=1
fi
# Tag silence toggle (default: silent=1)
# - HAKO_SILENT_TAGS=1 → filter noisy tag lines (default)
# - HAKO_SILENT_TAGS=0 → show raw logs (no filtering)
export HAKO_SILENT_TAGS="${HAKO_SILENT_TAGS:-1}"
# グローバル変数
export SMOKES_V2_LIB_LOADED=1
export SMOKES_START_TIME=$(date +%s.%N)
export SMOKES_TEST_COUNT=0
export SMOKES_PASS_COUNT=0
export SMOKES_FAIL_COUNT=0
export SMOKES_INCLUDE_SKIP_COUNT=0
declare -a SMOKES_INCLUDE_SKIP_LIST=()
# 色定義(重複回避)
if [ -z "${RED:-}" ]; then
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
fi
# ログ関数
log_info() {
echo -e "${BLUE}[INFO]${NC} $*" >&2
}
log_success() {
echo -e "${GREEN}[PASS]${NC} $*" >&2
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $*" >&2
}
log_error() {
echo -e "${RED}[FAIL]${NC} $*" >&2
}
# 共通イズフィルタVM実行時の出力整形
filter_noise() {
# Show raw logs (no filtering) to allow call traces / diagnostics
if [ "${HAKO_SHOW_CALL_LOGS:-0}" = "1" ] || [ "${HAKO_SILENT_TAGS}" = "0" ]; then
cat
return
fi
# プラグイン初期化やメタログ、動的ローダの案内等を除去
grep -v "^\[UnifiedBoxRegistry\]" \
| grep -v "^\[FileBox\]" \
| grep -v "^\[provider-registry\]" \
| grep -v "^\[provider/select:" \
| grep -v "^\[deprecate/env\]" \
| grep -v "^\[plugin/missing\]" \
| grep -v "^\[plugin/hint\]" \
| grep -v "^Net plugin:" \
| grep -v "^\[.*\] Plugin" \
| grep -v "Using builtin StringBox" \
| grep -v "Using builtin ArrayBox" \
| grep -v "Using builtin MapBox" \
| grep -v "^\[using\]" \
| grep -v "^\[using/resolve\]" \
| grep -v "^\[using/text-merge\]" \
| grep -v "^\[builder\]" \
| grep -v "^\\[vm-trace\\]" \
| grep -v '^\[PluginBoxFactory\]' \
| grep -v '^\[using.dylib/autoload\]' \
| grep -v "^\[vm\] Stage-3" \
| grep -v "^\[DEBUG\]" \
| grep -v '^\{"ev":' \
| grep -v '^\[warn\]' \
| grep -v '^\[error\]' \
| grep -v '^RC: ' \
| grep -v '^\[mirbuilder/normalize:' \
| grep -v '^\[warn\] dev fallback: user instance BoxCall' \
| sed -E 's/^❌ VM fallback error: *//' \
| grep -v '^\[warn\] dev verify: NewBox ' \
| grep -v '^\[warn\] dev verify: NewBox→birth invariant warnings:' \
| grep -v '^\[ny-compiler\]' \
| grep -v '^\[using/cache\]' \
| grep -v "plugins/nyash-array-plugin" \
| grep -v "plugins/nyash-map-plugin" \
| grep -v "Phase 15.5: Everything is Plugin" \
| grep -v "cargo build -p nyash-string-plugin" \
| grep -v "^\[plugin-loader\] backend=" \
| grep -v "^\[using\] ctx:" \
| grep -v "^🔌 plugin host initialized" \
| grep -v "^✅ plugin host fully configured" \
| grep -v "Failed to load nyash.toml - plugins disabled" \
| grep -v "^⚠️ Failed to load plugin config (hakorune.toml/nyash.toml) - plugins disabled" \
| grep -v "^🚀 Nyash VM Backend - Executing file:" \
| grep -v "^🚀 Hakorune VM Backend - Executing file:"
}
# 環境チェック(必須)
require_env() {
local required_tools=("cargo" "grep" "jq")
local missing_tools=()
for tool in "${required_tools[@]}"; do
if ! command -v "$tool" &> /dev/null; then
missing_tools+=("$tool")
fi
done
if [ ${#missing_tools[@]} -ne 0 ]; then
log_error "Required tools missing: ${missing_tools[*]}"
log_error "Please install missing tools and try again"
return 1
fi
# Nyash実行ファイル確認
if [ ! -f "$NYASH_BIN" ]; then
log_error "Nyash executable not found at $NYASH_BIN"
log_error "Please run 'cargo build --release' first (in $NYASH_ROOT)"
return 1
fi
log_info "Environment check passed"
return 0
}
# プラグイン整合性チェック(必須)
preflight_plugins() {
# プラグインマネージャーが存在する場合は実行
if [ -f "$(dirname "${BASH_SOURCE[0]}")/plugin_manager.sh" ]; then
source "$(dirname "${BASH_SOURCE[0]}")/plugin_manager.sh"
check_plugin_integrity || return 1
else
log_warn "Plugin manager not found, skipping plugin checks"
fi
return 0
}
# テスト実行関数
run_test() {
local test_name="$1"
local test_func="$2"
((SMOKES_TEST_COUNT++))
local start_time=$(date +%s.%N)
log_info "Running test: $test_name"
if $test_func; then
local end_time=$(date +%s.%N)
local duration=$(echo "$end_time - $start_time" | bc -l)
log_success "$test_name (${duration}s)"
((SMOKES_PASS_COUNT++))
return 0
else
local end_time=$(date +%s.%N)
local duration=$(echo "$end_time - $start_time" | bc -l)
log_error "$test_name (${duration}s)"
((SMOKES_FAIL_COUNT++))
return 1
fi
}
# Nyash実行ヘルパーRust VM
run_nyash_vm() {
local program="$1"
shift
local USE_PYVM="${SMOKES_USE_PYVM:-0}"
local EXTRA_ARGS=()
if [ "${SMOKES_USE_DEV:-0}" = "1" ]; then
EXTRA_ARGS+=("--dev")
fi
# Optional env sanitization between rapid invocations (default OFF)
# Enable with: SMOKES_CLEAN_ENV=1
local ENV_PREFIX=( )
if [ "${SMOKES_CLEAN_ENV:-0}" = "1" ]; then
# Preserve NYASH_JSON_ONLY to allow quiet JSON pipelines (e.g., v1 emitters)
ENV_PREFIX=(env -u NYASH_DEBUG_ENABLE -u NYASH_DEBUG_KINDS -u NYASH_DEBUG_SINK \
-u NYASH_RESOLVE_FIX_BRACES -u NYASH_USING_AST \
-u NYASH_VM_TRACE -u NYASH_VM_VERIFY_MIR -u NYASH_VM_TOLERATE_VOID \
-u NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN)
fi
# -c オプションの場合は一時ファイル経由で実行
if [ "$program" = "-c" ]; then
local code="$1"
shift
local tmpfile="/tmp/nyash_test_$$.hako"
echo "$code" > "$tmpfile"
# (shim removed) provider tag shortcut — hv1 inline is stable now
# 軽量ASIFixテスト用: ブロック終端の余剰セミコロンを寛容に除去
if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ]; then
sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$tmpfile" || true
fi
# プラグイン初期化メッセージを除外
# Optional preinclude for include-based code
local runfile="$tmpfile"
if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then
local prefile="/tmp/nyash_pre_$$.hako"
"$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$tmpfile" "$prefile" >/dev/null || true
runfile="$prefile"
fi
# Optional hint for include lines when preinclude is OFF
if grep -q '^include\s\"' "$tmpfile" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then
echo "[WARN] VM backend does not support include. Prefer using+alias, or set NYASH_PREINCLUDE=1 for dev." >&2
fi
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \
NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} \
NYASH_USING_AST=1 NYASH_PARSER_SEAM_TOLERANT=1 \
"${ENV_PREFIX[@]}" \
"$NYASH_BIN" --backend vm "$runfile" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise
local exit_code=${PIPESTATUS[0]}
# prefile may be unset when preinclude is OFF; use default expansion to avoid set -u errors
rm -f "$tmpfile" "${prefile:-}" 2>/dev/null || true
if [ "${SMOKES_FORCE_ZERO:-0}" = "1" ]; then
return 0
fi
return $exit_code
else
# 軽量ASIFixテスト用: ブロック終端の余剰セミコロンを寛容に除去
if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then
sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true
fi
# プラグイン初期化メッセージを除外
# Optional preinclude
local runfile2="$program"
if [ "${NYASH_PREINCLUDE:-0}" = "1" ] || [ "${HAKO_PREINCLUDE:-0}" = "1" ]; then
local prefile2="/tmp/nyash_pre_$$.hako"
"$NYASH_ROOT/tools/dev/hako_preinclude.sh" "$program" "$prefile2" >/dev/null || true
runfile2="$prefile2"
fi
# Optional hint for include lines when preinclude is OFF
if [ -f "$program" ] && grep -q '^include\s\"' "$program" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then
# Policy: quick は SKIP 既定。それ以外は WARNSMOKES_INCLUDE_POLICY で上書き可能)。
local policy="${SMOKES_INCLUDE_POLICY:-}"
if [ -z "$policy" ]; then
case "$program" in
*/profiles/quick/*) policy="error" ;;
*) policy="warn" ;;
esac
fi
if [ "$policy" = "skip" ]; then
SMOKES_INCLUDE_SKIP_COUNT=$((SMOKES_INCLUDE_SKIP_COUNT + 1))
local rel_path="$program"
if [[ "$program" == "$NYASH_ROOT/"* ]]; then
rel_path="${program#$NYASH_ROOT/}"
fi
SMOKES_INCLUDE_SKIP_LIST+=("$rel_path")
echo "[SKIP] include is deprecated in 20.36+ (quick). Prefer using+alias." >&2
return 0
elif [ "$policy" = "error" ]; then
echo "[ERROR] include is deprecated in 20.36+. Prefer using+alias." >&2
return 2
else
echo "[WARN] include is deprecated in 20.36+. Prefer using+alias. Preinclude is dev-only (NYASH_PREINCLUDE=1)." >&2
fi
fi
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \
NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} \
NYASH_USING_AST=1 NYASH_PARSER_SEAM_TOLERANT=1 \
"${ENV_PREFIX[@]}" \
"$NYASH_BIN" --backend vm "$runfile2" "${EXTRA_ARGS[@]}" "$@" 2>&1 | filter_noise
local exit_code=${PIPESTATUS[0]}
# prefile2 may be unset when preinclude is OFF
rm -f "${prefile2:-}" 2>/dev/null || true
if [ "${SMOKES_FORCE_ZERO:-0}" = "1" ]; then
return 0
fi
return $exit_code
fi
}
# Verify MIR JSON rc using selected primary (Core or Hakorune VM)
verify_mir_rc() {
local json_path="$1"
# 20.36: hakovm を primary 既定へCore は診断 fallback
local primary="${HAKO_VERIFY_PRIMARY:-hakovm}"
if [ "$primary" = "hakovm" ]; then
# For MIR JSON v1, try Hakovm v1 dispatcher first (default ON), fallback to Core on failure.
# Allow forcing Core with HAKO_VERIFY_V1_FORCE_CORE=1
if grep -q '"schema_version"' "$json_path" 2>/dev/null; then
if [ "${HAKO_VERIFY_V1_FORCE_CORE:-0}" = "1" ]; then
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi
"$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1; return $?
fi
# hv1 直行main.rs 早期経路)。成功時は rc を採用、失敗時は Core にフォールバック。
# ただしフロー検証dispatcher flow / phi 実験)が有効な場合は Core を優先hv1-inline は最小実装のため)。
if [ "${HAKO_VERIFY_V1_FORCE_HAKOVM:-0}" != "1" ]; then
local hv1_rc; hv1_rc=$(verify_v1_inline_file "$json_path" || true)
if [[ "$hv1_rc" =~ ^-?[0-9]+$ ]]; then
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hv1_inline (rust)" >&2; fi
local n=$hv1_rc; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi; return $n
fi
fi
# 強制 hv1-c ラッパ): NyVmDispatcherV1Box.run を直接呼び出して rc を取得
if [ "${HAKO_VERIFY_V1_FORCE_HAKOVM:-0}" = "1" ]; then
local hv1_rc_force; hv1_rc_force=$(verify_v1_inline_file "$json_path" || true)
if [[ "$hv1_rc_force" =~ ^-?[0-9]+$ ]]; then
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hv1_inline (rust)" >&2; fi
local n=$hv1_rc_force; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi; return $n
fi
return 1
fi
# No include+preinclude fallback succeeded → Core にフォールバック
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi
"$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1
return $?
fi
# Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded
if [ ! -f "$json_path" ]; then
echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2
return 2
fi
# Escape JSON as a single string literal via jq -Rs (preserves newlines)
local json_literal
json_literal="$(jq -Rs . < "$json_path")"
build_and_run_driver_alias() {
local header="$1"
local code=$(cat <<HCODE
$header
static box Main { method main(args) {
local j = __MIR_JSON__;
local rc = MiniVmEntryBox.run_min(j)
print(MiniVmEntryBox.int_to_str(rc))
return rc
} }
HCODE
)
code="${code/__MIR_JSON__/$json_literal}"
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}'
}
build_and_run_driver_include() {
local inc_path="$1"
local code=$(cat <<HCODE
include "$inc_path"
static box Main { method main(args) {
local j = __MIR_JSON__;
local rc = MiniVmEntryBox.run_min(j)
print(MiniVmEntryBox.int_to_str(rc))
return rc
} }
HCODE
)
code="${code/__MIR_JSON__/$json_literal}"
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 NYASH_PREINCLUDE=1 NYASH_RESOLVE_FIX_BRACES=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}'
}
# Try alias header first; fallback to dev-file header; final fallback: include+preinclude
local out
out="$(build_and_run_driver_alias 'using selfhost.vm.entry as MiniVmEntryBox')"
if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then
out="$(build_and_run_driver_alias 'using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox')"
fi
if ! [[ "$out" =~ ^-?[0-9]+$ ]]; then
out="$(build_and_run_driver_include 'lang/src/vm/boxes/mini_vm_entry.hako')"
fi
if [[ "$out" =~ ^-?[0-9]+$ ]]; then
local n=$out
# normalize into [0,255]
if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi
return $n
fi
# Fallback: core primary when MiniVM resolution is unavailable
if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then
local json_literal3; json_literal3="$(jq -Rs . < "$json_path")"
local code=$(cat <<HCODE
include "lang/src/vm/core/dispatcher.hako"
static box Main { method main(args) {
local j = __MIR_JSON__
local r = NyVmDispatcher.run(j)
print("" + r)
return r
} }
HCODE
)
code="${code/__MIR_JSON__/$json_literal3}"
local tmpwrap="/tmp/hako_core_wrap_$$.hako"
echo "$code" > "$tmpwrap"
NYASH_PREINCLUDE=1 run_nyash_vm "$tmpwrap" >/dev/null 2>&1; local r=$?; rm -f "$tmpwrap"; return $r
fi
NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $?
else
# Core primary: detect MIR(JSON) vs Program(JSON v0)
if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then
"$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1; return $?
fi
NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $?
fi
}
# New function: verify_program_via_builder_to_core
# Purpose: Program(JSON v0) → MirBuilder(Hako) → MIR(JSON v0) → Core execution
# This is dev-only for testing builder output quality
verify_program_via_builder_to_core() {
local prog_json_path="$1"
# Step 1: Use minimal runner to convert Program → MIRenv経由でJSONを渡す
local mir_json_path="/tmp/builder_output_$$.json"
local builder_code_min=$(cat <<'HCODE'
using "hako.mir.builder.internal.runner_min" as BuilderRunnerMinBox
static box Main { method main(args) {
local prog_json = env.get("HAKO_BUILDER_PROGRAM_JSON")
if prog_json == null { print("Builder failed"); return 1 }
local mir_out = BuilderRunnerMinBox.run(prog_json)
if mir_out == null { print("Builder failed"); return 1 }
print("[MIR_OUT_BEGIN]")
print("" + mir_out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
)
# Read program JSON to env (avoid embedding/escaping)
local prog_json_raw
prog_json_raw="$(cat "$prog_json_path")"
# Run builder with internal lowers enabled using v1 dispatcher
local mir_json=""
local builder_stderr="/tmp/builder_stderr_$$.log"
local builder_stdout="/tmp/builder_stdout_$$.log"
# Try minimal runner first (fast path), unless HAKO_PREFER_MIRBUILDER=1
if [ "${HAKO_PREFER_MIRBUILDER:-0}" = "1" ]; then
: # skip minimal runner
else
mir_json=$(HAKO_MIR_BUILDER_INTERNAL=1 \
HAKO_MIR_RUNNER_MIN_NO_METHODS=1 \
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \
HAKO_ROUTE_HAKOVM=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
NYASH_USING_AST=1 \
NYASH_RESOLVE_FIX_BRACES=1 \
NYASH_DISABLE_NY_COMPILER=1 \
NYASH_PARSER_STAGE3=1 \
HAKO_PARSER_STAGE3=1 \
NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json_raw" \
run_nyash_vm -c "$builder_code_min" 2>"$builder_stderr" | tee "$builder_stdout" | awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag')
fi
if [ "${HAKO_MIR_BUILDER_DEBUG:-0}" = "1" ]; then
echo "[builder debug] stdout (tail):" >&2
tail -n 60 "$builder_stdout" >&2 || true
echo "[builder debug] stderr (tail):" >&2
tail -n 60 "$builder_stderr" >&2 || true
fi
# Fallback Option A: try full MirBuilderBox (emit) when minimal runner fails
if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then
local builder_code_full=$(cat <<'HCODE'
using "hako.mir.builder" as MirBuilderBox
static box Main { method main(args) {
local prog_json = env.get("HAKO_BUILDER_PROGRAM_JSON")
if prog_json == null { print("Builder failed"); return 1 }
local mir_out = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
if mir_out == null { print("Builder failed"); return 1 }
print("[MIR_OUT_BEGIN]")
print("" + mir_out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
)
mir_json=$(HAKO_MIR_BUILDER_INTERNAL=1 \
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 \
HAKO_ROUTE_HAKOVM=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \
NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 \
NYASH_DISABLE_NY_COMPILER=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json_raw" \
run_nyash_vm -c "$builder_code_full" 2>>"$builder_stderr" | tee -a "$builder_stdout" | awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag')
fi
# PRIMARY no-fallback: if requested, do not fall back to Rust CLI builder
if [ "${HAKO_PRIMARY_NO_FALLBACK:-0}" = "1" ]; then
if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then
return 1
fi
fi
# Fallback Option B: use Rust CLI builder when Hako builder fails
if [ "$mir_json" = "Builder failed" ] || [ -z "$mir_json" ]; then
if [ "${HAKO_MIR_BUILDER_DEBUG:-0}" = "1" ] && [ -f "$builder_stderr" ]; then
echo "[builder debug] Hako builder failed, falling back to Rust CLI" >&2
cat "$builder_stderr" >&2
cp "$builder_stderr" /tmp/builder_last_error.log
fi
rm -f "$builder_stderr"
local tmp_mir="/tmp/ny_builder_conv_$$.json"
if "$NYASH_BIN" --program-json-to-mir "$tmp_mir" --json-file "$prog_json_path" >/dev/null 2>&1; then
if [ "${HAKO_VERIFY_BUILDER_ONLY:-0}" = "1" ]; then
# Builder-only: check structure only
if grep -q '"functions"' "$tmp_mir" && grep -q '"blocks"' "$tmp_mir"; then
rm -f "$tmp_mir"; return 0
else
rm -f "$tmp_mir"; return 1
fi
else
"$NYASH_BIN" --mir-json-file "$tmp_mir" >/dev/null 2>&1
local rc=$?
rm -f "$tmp_mir"
return $rc
fi
else
return 1
fi
fi
rm -f "$builder_stderr" "$builder_stdout"
# Validate builder output looks like MIR JSON; otherwise fallback to Rust CLI (unless PRIMARY no-fallback)
if ! echo "$mir_json" | grep -q '"functions"' || ! echo "$mir_json" | grep -q '"blocks"'; then
if [ "${HAKO_PRIMARY_NO_FALLBACK:-0}" = "1" ]; then
return 1
fi
# fallback: Rust CLI builder
local tmp_mir="/tmp/ny_builder_conv_$$.json"
if "$NYASH_BIN" --program-json-to-mir "$tmp_mir" --json-file "$prog_json_path" >/dev/null 2>&1; then
"$NYASH_BIN" --mir-json-file "$tmp_mir" >/dev/null 2>&1
local rc=$?
rm -f "$tmp_mir"
return $rc
else
return 1
fi
fi
# Route: if builder output contains v1 hints, run hv1 dispatcher inline.
if echo "$mir_json" | grep -q '"schema_version"' || echo "$mir_json" | grep -q '"op"\s*:\s*"mir_call"'; then
local hv1_rc
local mir_literal; mir_literal="$(printf '%s' "$mir_json" | jq -Rs .)"
hv1_rc=$(run_hv1_inline_alias_wrapper "$mir_literal")
if [[ "$hv1_rc" =~ ^-?[0-9]+$ ]]; then
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: hakovm (hako)" >&2; fi
local n=$hv1_rc; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi
return $n
fi
fi
# Route: if builder output is v0 but contains unified-only ops (newbox/boxcall),
# execute via Hako Core dispatcher (NyVmDispatcher.run) which supports extended v0.
if echo "$mir_json" | grep -q '"op"\s*:\s*"newbox"' || echo "$mir_json" | grep -q '"op"\s*:\s*"boxcall"'; then
local mir_literal2; mir_literal2="$(printf '%s' "$mir_json" | jq -Rs .)"
local code=$(cat <<'HCODE'
include "lang/src/vm/core/dispatcher.hako"
static box Main { method main(args) {
local j = env.get("NYASH_VERIFY_JSON")
local r = NyVmDispatcher.run(j)
print("" + r)
return r
} }
HCODE
)
local out; out=$(NYASH_VERIFY_JSON="$mir_literal2" NYASH_PREINCLUDE=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}')
if [[ "$out" =~ ^-?[0-9]+$ ]]; then
local n=$out; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi
return $n
fi
fi
# Optional: structure-only check (builder only) for fast mode
if [ "${HAKO_VERIFY_BUILDER_ONLY:-0}" = "1" ]; then
if echo "$mir_json" | grep -q '"functions"' && echo "$mir_json" | grep -q '"blocks"'; then
return 0
else
return 1
fi
fi
# Write MIR JSON to temp file and execute via Core
echo "$mir_json" > "$mir_json_path"
if [ "${HAKO_TRACE_EXECUTION:-0}" = "1" ]; then echo "[trace] executor: core (rust)" >&2; fi
"$NYASH_BIN" --mir-json-file "$mir_json_path" >/dev/null 2>&1
local rc=$?
rm -f "$mir_json_path"
return $rc
}
# hv1 inline alias-only wrapper (env JSON → hv1 dispatcher)
# Usage: run_hv1_inline_alias_wrapper "$json_literal" → prints rc line; returns rc
run_hv1_inline_alias_wrapper() {
local json_literal="$1"
local code=$(cat <<'HCODE'
using "selfhost.vm.hv1.dispatch" as NyVm
static box Main { method main(args) {
local j = env.get("NYASH_VERIFY_JSON")
local r = NyVm.NyVmDispatcherV1Box.run(j)
print("" + r)
return r
} }
HCODE
)
HAKO_FAIL_FAST_ON_HAKO_IN_NYASH_VM=0 NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 \
NYASH_VERIFY_JSON="$json_literal" run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}'
}
# Nyash実行ヘルパーLLVM
run_nyash_llvm() {
local program="$1"
shift
# Allow developer to force LLVM run (env guarantees availability)
if [ "${SMOKES_FORCE_LLVM:-0}" != "1" ]; then
# Skip gracefully when LLVM backend is not available in this build
# Primary check: version string advertises features
if ! "$NYASH_BIN" --version 2>/dev/null | grep -q "features.*llvm"; then
# Fallback check: binary contains LLVM harness symbols (ny-llvmc / NYASH_LLVM_USE_HARNESS)
if ! strings "$NYASH_BIN" 2>/dev/null | grep -E -q 'ny-llvmc|NYASH_LLVM_USE_HARNESS'; then
log_warn "LLVM backend not available in this build; skipping LLVM run"
log_info "Hint: build ny-llvmc + enable harness: cargo build --release -p nyash-llvm-compiler && cargo build --release --features llvm"
return 0
fi
fi
fi
# -c オプションの場合は一時ファイル経由で実行
if [ "$program" = "-c" ]; then
local code="$1"
shift
local tmpfile="/tmp/nyash_test_$$.hako"
echo "$code" > "$tmpfile"
# 軽量ASIFixテスト用: ブロック終端の余剰セミコロンを寛容に除去
if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ]; then
sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$tmpfile" || true
fi
# 軽量ASIFixテスト用: ブロック終端の余剰セミコロンを寛容に除去
if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then
sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true
fi
# プラグイン初期化メッセージを除外
PYTHONPATH="${PYTHONPATH:-$NYASH_ROOT}" NYASH_NY_LLVM_COMPILER="$NYASH_ROOT/target/release/ny-llvmc" NYASH_LLVM_USE_HARNESS=1 NYASH_EMIT_EXE_NYRT="$NYASH_ROOT/target/release" NYASH_VM_USE_PY=0 NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 "$NYASH_BIN" --backend llvm "$tmpfile" "$@" 2>&1 | \
grep -v "^\[UnifiedBoxRegistry\]" | grep -v "^\[FileBox\]" | grep -v "^Net plugin:" | grep -v "^\[.*\] Plugin" | \
grep -v '^\[plugin-loader\] backend=' | \
grep -v '^🔌 plugin host initialized' | grep -v '^✅ plugin host fully configured' | \
grep -v '^⚡ Hakorune LLVM Backend' | \
grep -v '^✅ LLVM (harness) execution completed' | grep -v '^📊 MIR Module compiled successfully' | grep -v '^📊 Functions:' | grep -v 'JSON Parse Errors:' | grep -v 'Parsing errors' | grep -v 'No parsing errors' | grep -v 'Error at line ' | \
grep -v '^\[using\]' | grep -v '^\[using/resolve\]' | grep -v '^\[using/cache\]' | \
grep -v '^\[ny-llvmc\]' | grep -v '^\[harness\]' | grep -v '^Compiled to ' | grep -v '^/usr/bin/ld:'
local exit_code=${PIPESTATUS[0]}
rm -f "$tmpfile"
return $exit_code
else
# 軽量ASIFixテスト用: ブロック終端の余剰セミコロンを寛容に除去
if [ "${SMOKES_ASI_STRIP_SEMI:-1}" = "1" ] && [ -f "$program" ]; then
sed -i -E 's/;([[:space:]]*)(\}|$)/\1\2/g' "$program" || true
fi
# プラグイン初期化メッセージを除外
PYTHONPATH="${PYTHONPATH:-$NYASH_ROOT}" NYASH_NY_LLVM_COMPILER="$NYASH_ROOT/target/release/ny-llvmc" NYASH_LLVM_USE_HARNESS=1 NYASH_EMIT_EXE_NYRT="$NYASH_ROOT/target/release" NYASH_VM_USE_PY=0 NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 "$NYASH_BIN" --backend llvm "$program" "$@" 2>&1 | \
grep -v "^\[UnifiedBoxRegistry\]" | grep -v "^\[FileBox\]" | grep -v "^Net plugin:" | grep -v "^\[.*\] Plugin" | \
grep -v '^\[plugin-loader\] backend=' | \
grep -v '^🔌 plugin host initialized' | grep -v '^✅ plugin host fully configured' | \
grep -v '^⚡ Hakorune LLVM Backend' | \
grep -v '^✅ LLVM (harness) execution completed' | grep -v '^📊 MIR Module compiled successfully' | grep -v '^📊 Functions:' | grep -v 'JSON Parse Errors:' | grep -v 'Parsing errors' | grep -v 'No parsing errors' | grep -v 'Error at line ' | \
grep -v '^\[using\]' | grep -v '^\[using/resolve\]' | grep -v '^\[using/cache\]' | \
grep -v '^\[ny-llvmc\]' | grep -v '^\[harness\]' | grep -v '^Compiled to ' | grep -v '^/usr/bin/ld:'
return ${PIPESTATUS[0]}
fi
}
# シンプルテスト補助(スクリプト互換)
test_pass() { log_success "$1"; return 0; }
test_fail() { log_error "$1 ${2:-}"; return 1; }
test_skip() { log_warn "SKIP $1 ${2:-}"; return 0; }
# 出力比較ヘルパー
compare_outputs() {
local expected="$1"
local actual="$2"
local test_name="$3"
if [ "$expected" = "$actual" ]; then
return 0
else
log_error "$test_name output mismatch:"
log_error " Expected: $expected"
log_error " Actual: $actual"
return 1
fi
}
# 結果サマリー出力
print_summary() {
local end_time=$(date +%s.%N)
local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l)
echo ""
echo "==============================================="
echo "Smoke Test Summary"
echo "==============================================="
echo "Total tests: $SMOKES_TEST_COUNT"
echo "Passed: $SMOKES_PASS_COUNT"
echo "Failed: $SMOKES_FAIL_COUNT"
echo "Duration: ${total_duration}s"
echo ""
if [ "${SMOKES_INCLUDE_SKIP_COUNT:-0}" -gt 0 ]; then
echo "Include SKIPs: $SMOKES_INCLUDE_SKIP_COUNT"
for entry in "${SMOKES_INCLUDE_SKIP_LIST[@]}"; do
echo " - $entry"
done
echo ""
fi
if [ $SMOKES_FAIL_COUNT -eq 0 ]; then
log_success "All tests passed! ✨"
return 0
else
log_error "$SMOKES_FAIL_COUNT test(s) failed"
return 1
fi
}
# JSON出力関数
output_json() {
local profile="${1:-unknown}"
local end_time=$(date +%s.%N)
local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l)
cat << EOF
{
"profile": "$profile",
"total": $SMOKES_TEST_COUNT,
"passed": $SMOKES_PASS_COUNT,
"failed": $SMOKES_FAIL_COUNT,
"duration": $total_duration,
"timestamp": "$(date -Iseconds)",
"success": $([ $SMOKES_FAIL_COUNT -eq 0 ] && echo "true" || echo "false")
}
EOF
}
# JUnit XML出力関数
output_junit() {
local profile="${1:-unknown}"
local end_time=$(date +%s.%N)
local total_duration=$(echo "$end_time - $SMOKES_START_TIME" | bc -l)
cat << EOF
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="smokes_$profile" tests="$SMOKES_TEST_COUNT" failures="$SMOKES_FAIL_COUNT" time="$total_duration">
<!-- Individual test cases would be added by specific test scripts -->
</testsuite>
EOF
}
# v1 JSON 正規化(オブジェクトキー順序ソート、配列順は保持)→ SHA256 ハッシュ
v1_normalized_hash() {
local json_path="$1"
if [ ! -f "$json_path" ]; then
echo "[FAIL] v1_normalized_hash: json not found: $json_path" >&2
return 2
fi
if ! command -v jq >/dev/null 2>&1; then
echo "[FAIL] v1_normalized_hash: jq required" >&2
return 2
fi
# Extract JSON object line defensively (strip any leading noise like 'RC: N' or logs)
# Extract JSON object block from first '{' to EOF (handles pretty JSON)
local raw_json
raw_json=$(awk 'BEGIN{on=0} { if(on){print} else if($0 ~ /^[[:space:]]*\{/){ on=1; print } }' "$json_path")
if [ -z "$raw_json" ]; then
return 1
fi
local canon
canon=$(printf '%s' "$raw_json" | jq -S -c .) || return 1
printf "%s" "$canon" | sha256sum | awk '{print $1}'
}
# 2つの v1 JSON ファイルの正規化ハッシュを比較等しければ0
compare_v1_hash() {
local a="$1"; local b="$2"
local ha hb
ha=$(v1_normalized_hash "$a" || true)
hb=$(v1_normalized_hash "$b" || true)
if [ -z "$ha" ] || [ -z "$hb" ]; then
return 2
fi
[ "$ha" = "$hb" ]
}
# Run hv1 inline (early route) and return numeric rc (0-255). Returns non-zero exit on failure to execute.
verify_v1_inline_file() {
local json_path="$1"
if [ ! -f "$json_path" ]; then
echo "[FAIL] verify_v1_inline_file: json not found: $json_path" >&2
return 2
fi
local out
# Optional: show full logs for debugging (default OFF)
if [ "${HAKO_VERIFY_SHOW_LOGS:-0}" = "1" ]; then
# Show all output to stderr, then extract numeric rc (env-sanitized for determinism)
env -i PATH="$PATH" \
HAKO_TRACE_EXECUTION="${HAKO_TRACE_EXECUTION:-0}" HAKO_VERIFY_SHOW_LOGS="${HAKO_VERIFY_SHOW_LOGS:-0}" \
HAKO_ROUTE_HAKOVM=1 HAKO_VERIFY_V1_FORCE_HAKOVM=1 \
NYASH_VERIFY_JSON="$(cat "$json_path")" \
"$NYASH_BIN" --backend vm /dev/null 2>&1 | tr -d '\r' | tee /tmp/hv1_debug.log >&2
out=$(awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}' /tmp/hv1_debug.log)
else
out=$(env -i PATH="$PATH" \
HAKO_TRACE_EXECUTION="${HAKO_TRACE_EXECUTION:-0}" \
HAKO_ROUTE_HAKOVM=1 HAKO_VERIFY_V1_FORCE_HAKOVM=1 \
NYASH_VERIFY_JSON="$(cat "$json_path")" \
"$NYASH_BIN" --backend vm /dev/null 2>/dev/null | tr -d '\r' | awk '/^-?[0-9]+$/{n=$0} END{if(n!="") print n}')
fi
if [[ "$out" =~ ^-?[0-9]+$ ]]; then
# echo numeric rc and return success; caller normalizes/returns as exit code
echo "$out"
return 0
fi
return 1
}
# Dev profile helpers (centralize bring-up toggles for MirBuilder)
# Usage: call enable_mirbuilder_dev_env in canaries that need it.
enable_mirbuilder_dev_env() {
# Avoid ny-compiler inline path during VM bring-up
export NYASH_USE_NY_COMPILER=0
# Allow FileBox provider fallback (dev only)
export NYASH_FAIL_FAST=${NYASH_FAIL_FAST:-0}
# Enable using resolution in VM
export NYASH_ENABLE_USING=1
export HAKO_ENABLE_USING=1
# Allow file-based using in dev (e.g., using "hako.mir.builder")
export NYASH_ALLOW_USING_FILE=1
export HAKO_ALLOW_USING_FILE=1
# Optional: preinclude heavy using segments for legacy/prelude-heavy paths (default OFF)
if [ "${SMOKES_DEV_PREINCLUDE:-0}" = "1" ]; then
export HAKO_PREINCLUDE=1
fi
# Optional: enable JsonFrag Normalizer for builder/min paths (default OFF)
# Use only in targeted canaries; keep OFF for general runs
if [ "${SMOKES_DEV_NORMALIZE:-0}" = "1" ]; then
export HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1
fi
# Profile-based injection example (commented; enable when needed):
# if [ "${SMOKES_ENABLE_NORMALIZE_FOR_QUICK:-0}" = "1" ] && [ "${SMOKES_CURRENT_PROFILE:-}" = "quick" ]; then
# export HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1
# export HAKO_MIR_BUILDER_NORMALIZE_TAG=1 # optional: show tags in logs for diagnostics
# fi
}
# Dev profile helpers (EXE/AOT bring-up)
# Sets environment defaults for LLVM crate backend and EXE link paths.
# Usage: call enable_exe_dev_env in EXE canaries.
enable_exe_dev_env() {
# Prefer crate backend when available
export NYASH_LLVM_BACKEND=${NYASH_LLVM_BACKEND:-crate}
# Tool locations (override when cross)
export NYASH_NY_LLVM_COMPILER=${NYASH_NY_LLVM_COMPILER:-"$NYASH_ROOT/target/release/ny-llvmc"}
# NyRT (kernel) lib search path for linking EXEs
export NYASH_EMIT_EXE_NYRT=${NYASH_EMIT_EXE_NYRT:-"$NYASH_ROOT/target/release"}
# Optional verification toggles (kept ON by default only for canaries that opt-in)
export NYASH_LLVM_VERIFY=${NYASH_LLVM_VERIFY:-0}
export NYASH_LLVM_VERIFY_IR=${NYASH_LLVM_VERIFY_IR:-0}
}