Files
hakorune/tools/hakorune_emit_mir.sh

799 lines
30 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.

#!/usr/bin/env bash
# hakorune_emit_mir.sh — Emit MIR(JSON) using Hakorune StageB + MirBuilder (Hakofirst)
#
# Usage: tools/hakorune_emit_mir.sh <input.hako> <out.json>
# Notes:
# - Runs the StageB compiler (Hako) to emit Program(JSON v0), then feeds it to MirBuilderBox.emit_from_program_json_v0.
# - Defaults to the Hakorune VM path; no inline Ny compiler; Stage3 enabled.
# - Keeps defaults conservative: no global flips; this is a helper for dev/CI scripts.
set -euo pipefail
if [ "$#" -ne 2 ]; then
echo "Usage: $0 <input.hako> <out.json>" >&2
exit 2
fi
IN="$1"
OUT="$2"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then
ROOT="$ROOT_GIT"
else
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
fi
# Resolve nyash/hakorune binary via test_runner helper (ensures consistent env)
if [ ! -f "$IN" ]; then
echo "[FAIL] input not found: $IN" >&2
exit 1
fi
# Resolve nyash/hakorune binary (simple detection; test_runner will override later if sourced)
if [ -z "${NYASH_BIN:-}" ]; then
if [ -x "$ROOT/target/release/hakorune" ]; then
export NYASH_BIN="$ROOT/target/release/hakorune"
else
export NYASH_BIN="$ROOT/target/release/nyash"
fi
fi
# Store CODE in temp file to avoid subshell expansion issues
CODE_TMP=$(mktemp --suffix=.hako)
trap 'rm -f "$CODE_TMP" || true' EXIT
cp "$IN" "$CODE_TMP"
# Check if FORCE jsonfrag mode is requested (bypasses Stage-B entirely)
if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then
# Extract limit from code using grep/awk
limit=$(cat "$CODE_TMP" | grep -o '[0-9]\+' | head -1 || echo "10")
# Generate minimal while-form MIR(JSON) directly (executable semantics)
# PHI incoming format: [[value_register, predecessor_block_id], ...]
echo "[emit/jsonfrag] FORCE min-loop MIR (dev-only)" >&2
cat > "$OUT" <<MIRJSON
{
"functions": [{
"name": "main",
"params": [],
"locals": [],
"blocks": [
{
"id": 0,
"instructions": [
{"op": "const", "dst": 1, "value": {"type": "i64", "value": 0}},
{"op": "const", "dst": 2, "value": {"type": "i64", "value": ${limit}}},
{"op": "jump", "target": 1}
]
},
{
"id": 1,
"instructions": [
{"op": "phi", "dst": 6, "incoming": [[2, 0], [6, 2]]},
{"op": "phi", "dst": 3, "incoming": [[1, 0], [5, 2]]},
{"op": "compare", "operation": "<", "lhs": 3, "rhs": 6, "dst": 4},
{"op": "branch", "cond": 4, "then": 2, "else": 3}
]
},
{
"id": 2,
"instructions": [
{"op": "const", "dst": 10, "value": {"type": "i64", "value": 1}},
{"op": "binop", "operation": "+", "lhs": 3, "rhs": 10, "dst": 5},
{"op": "jump", "target": 1}
]
},
{
"id": 3,
"instructions": [
{"op": "ret", "value": 3}
]
}
]
}]
}
MIRJSON
echo "[OK] MIR JSON written (force-jsonfrag): $OUT"
exit 0
fi
# 1) StageB: Hako parser emits Program(JSON v0) to stdout
# Extract Program JSON robustly using Python3 bracket balancing with fallbacks
extract_program_json_py() {
python3 - <<'PYEOF'
import sys
stdin = sys.stdin.read()
# Find the start of Program JSON (look for "kind":"Program")
start = stdin.find('"kind":"Program"')
if start < 0:
sys.exit(1)
# Walk back to find the opening brace of the object containing "kind":"Program"
pos = start
depth = 0
while pos >= 0:
if stdin[pos] == '{':
depth += 1
if depth == 1:
# Found the start brace
break
elif stdin[pos] == '}':
depth -= 1
pos -= 1
if pos < 0:
sys.exit(1)
# Now walk forward from pos, tracking braces to find the matching closing brace
obj_start = pos
depth = 0
in_string = False
escape = False
i = obj_start
while i < len(stdin):
ch = stdin[i]
if escape:
escape = False
elif in_string:
if ch == '\\':
escape = True
elif ch == '"':
in_string = False
else:
if ch == '"':
in_string = True
elif ch == '{':
depth += 1
elif ch == '}':
depth -= 1
if depth == 0:
# Found the matching closing brace
print(stdin[obj_start:i+1])
sys.exit(0)
i += 1
# If we get here, no matching brace found
sys.exit(1)
PYEOF
}
extract_program_json() {
local input="$1"
local result
# Try 1: Python balancer (existing)
result=$(echo "$input" | extract_program_json_py 2>/dev/null || true)
if [ -n "$result" ] && echo "$result" | grep -q '"kind".*"Program"'; then
echo "$result"
return 0
fi
# Try 2: Simple awk fallback
result=$(echo "$input" | awk '/^\{/,/^\}$/')
if [ -n "$result" ] && echo "$result" | grep -q '"kind".*"Program"'; then
echo "$result"
return 0
fi
# Try 3: Ruby fallback (if available)
if command -v ruby >/dev/null 2>&1; then
result=$(echo "$input" | ruby -e 'puts STDIN.read[/\{.*"kind".*"Program".*\}/m]' 2>/dev/null || true)
if [ -n "$result" ]; then
echo "$result"
return 0
fi
fi
# All fallbacks failed
return 1
}
set +e
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
code_len=$(wc -c < "$CODE_TMP" | tr -d ' ')
echo "[emit:trace] Stage-B: Starting parse of input (${code_len} chars)..." >&2
fi
# Run Stage-B with temp file (avoid subshell CODE variable expansion)
PROG_JSON_RAW=$(cd "$ROOT" && \
NYASH_JSON_ONLY=1 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_ENABLE_USING=${NYASH_ENABLE_USING:-1} HAKO_ENABLE_USING=${HAKO_ENABLE_USING:-1} \
"$NYASH_BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$(cat "$CODE_TMP")" 2>&1)
rc=$?
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[emit:trace] Stage-B: Raw output length=${#PROG_JSON_RAW} chars, rc=$rc" >&2
fi
# Extract Program JSON from raw output
PROG_JSON_OUT=$(extract_program_json "$PROG_JSON_RAW" 2>/dev/null || true)
extract_rc=$?
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
if [ $extract_rc -eq 0 ] && [ -n "$PROG_JSON_OUT" ]; then
echo "[emit:trace] Stage-B: SUCCESS - Generated Program(JSON) (${#PROG_JSON_OUT} chars)" >&2
# Show first 200 chars for validation
prog_head=$(printf '%s' "$PROG_JSON_OUT" | head -c 200)
echo "[emit:trace] Stage-B: prog_json_head: $prog_head..." >&2
else
echo "[emit:trace] Stage-B: FAILED - extract_rc=$extract_rc, output_len=${#PROG_JSON_OUT}" >&2
if [ -n "$PROG_JSON_RAW" ]; then
echo "[emit:trace] Stage-B: Raw output first 200 chars:" >&2
printf '%s' "$PROG_JSON_RAW" | head -c 200 >&2
echo "" >&2
fi
fi
fi
# Update rc to reflect extraction result
if [ $extract_rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then
rc=1
fi
set -e
# If Stage-B fails, skip to direct MIR emit paths (provider/legacy)
if [ $rc -ne 0 ] || [ -z "$PROG_JSON_OUT" ]; then
# Stage-B not available - fall back to legacy CLI path directly
# Skip the intermediate Program(JSON) step and emit MIR directly
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-1} \
"$NYASH_BIN" --emit-mir-json "$OUT" "$IN" >/dev/null 2>&1; then
echo "[OK] MIR JSON written (direct-emit): $OUT"
exit 0
fi
echo "[FAIL] Stage-B and direct MIR emit both failed" >&2
exit 1
fi
# Quick validation for Program(JSON v0)
if ! printf '%s' "$PROG_JSON_OUT" | grep -q '"kind"\s*:\s*"Program"'; then
# Invalid Program JSON - fall back to direct emit
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-1} \
"$NYASH_BIN" --emit-mir-json "$OUT" "$IN" >/dev/null 2>&1; then
echo "[OK] MIR JSON written (direct-emit-fallback): $OUT"
exit 0
fi
echo "[FAIL] StageB output invalid and direct emit failed" >&2
exit 1
fi
# 2) Convert Program(JSON v0) → MIR(JSON)
# Prefer selfhost builder first when explicitly requested; otherwise use delegate (GateC) for stability.
try_selfhost_builder() {
local prog_json="$1" out_path="$2"
# FORCE=1 direct assembly shortcut (dev toggle, bypasses using resolution)
if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then
# Extract limit from Program(JSON) using grep/awk
local limit=$(printf '%s' "$prog_json" | grep -o '"type":"Int","value":[0-9]*' | head -1 | grep -o '[0-9]*$' || echo "10")
# Generate minimal while-form MIR(JSON) directly (executable semantics)
# PHI incoming format: [[value_register, predecessor_block_id], ...]
cat > "$out_path" <<'MIRJSON'
{
"functions": [{
"name": "main",
"params": [],
"locals": [],
"blocks": [
{
"id": 0,
"instructions": [
{"op": "const", "dst": 1, "value": {"type": "i64", "value": 0}},
{"op": "const", "dst": 2, "value": {"type": "i64", "value": LIMIT_PLACEHOLDER}},
{"op": "jump", "target": 1}
]
},
{
"id": 1,
"instructions": [
{"op": "phi", "dst": 6, "incoming": [[2, 0], [6, 2]]},
{"op": "phi", "dst": 3, "incoming": [[1, 0], [5, 2]]},
{"op": "compare", "operation": "<", "lhs": 3, "rhs": 6, "dst": 4},
{"op": "branch", "cond": 4, "then": 2, "else": 3}
]
},
{
"id": 2,
"instructions": [
{"op": "const", "dst": 10, "value": {"type": "i64", "value": 1}},
{"op": "binop", "operation": "+", "lhs": 3, "rhs": 10, "dst": 5},
{"op": "jump", "target": 1}
]
},
{
"id": 3,
"instructions": [
{"op": "ret", "value": 3}
]
}
]
}]
}
MIRJSON
# Provider-first delegate: call env.mirbuilder.emit(prog_json) and capture v1 JSON
try_provider_emit() {
local prog_json="$1" out_path="$2"
local tmp_hako; tmp_hako=$(mktemp --suffix .hako)
cat >"$tmp_hako" <<'HCODE'
using "hako.mir.builder.internal.jsonfrag_normalizer" as NormBox
static box Main { method main(args) {
local p = env.get("HAKO_BUILDER_PROGRAM_JSON")
if p == null { print("[provider/emit:nojson]"); return 1 }
local a = new ArrayBox(); a.push(p)
local out = hostbridge.extern_invoke("env.mirbuilder", "emit", a)
// Optional normalization (dev): apply JsonFrag normalizer/purifier to provider output
{
local nv = env.get("HAKO_MIR_NORMALIZE_PROVIDER")
if nv != null && ("" + nv) == "1" {
local out_s = "" + out
out = NormBox.normalize_all(out_s)
}
}
print("[provider/emit:ok]")
print("[MIR_OUT_BEGIN]")
print("" + out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
local tmp_stdout; tmp_stdout=$(mktemp)
trap 'rm -f "$tmp_hako" "$tmp_stdout" || true' RETURN
set +e
(cd "$ROOT" && \
NYASH_DISABLE_PLUGINS=1 NYASH_FILEBOX_MODE="core-ro" \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json" \
"$NYASH_BIN" --backend vm "$tmp_hako" 2>&1 | tee "$tmp_stdout" >/dev/null)
local rc=$?
set -e
if [ $rc -ne 0 ] || ! grep -q "\[provider/emit:ok\]" "$tmp_stdout"; then
return 1
fi
local mir
mir=$(awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag' "$tmp_stdout")
if [ -z "$mir" ]; then return 1; fi
# Write raw MIR JSON first
printf '%s' "$mir" > "$out_path"
# Optional AOT prep stage (run_json; no FileBox)
# Trigger when HAKO_APPLY_AOT_PREP=1 or when fast/hoist/collections_hot are requested.
if [ "${HAKO_APPLY_AOT_PREP:-0}" = "1" ] || [ "${NYASH_AOT_COLLECTIONS_HOT:-0}" = "1" ] || [ "${NYASH_LLVM_FAST:-0}" = "1" ] || [ "${NYASH_MIR_LOOP_HOIST:-0}" = "1" ]; then
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:trace] Applying AotPrep.run_json to MIR JSON..." >&2
fi
_prep_hako=$(mktemp --suffix .hako)
cat > "$_prep_hako" <<'HAKO'
using selfhost.llvm.ir.aot_prep as AotPrepBox
static box Main { method main(args) {
local src = env.get("HAKO_PREP_INPUT")
if src == null || src == "" { print("[prep:skip:empty]"); return 0 }
local out = AotPrepBox.run_json(src)
print("[PREP_OUT_BEGIN]")
print(out)
print("[PREP_OUT_END]")
return 0
} }
HAKO
# Read MIR JSON and pass via env; capture between markers
_prep_stdout=$(mktemp)
_prep_stderr=$(mktemp)
set +e
HAKO_PREP_INPUT="$(cat "$out_path")" \
NYASH_FILEBOX_MODE=core-ro \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_USING_RESOLVER_FIRST=1 \
NYASH_AOT_COLLECTIONS_HOT=${NYASH_AOT_COLLECTIONS_HOT:-0} NYASH_LLVM_FAST=${NYASH_LLVM_FAST:-0} NYASH_MIR_LOOP_HOIST=${NYASH_MIR_LOOP_HOIST:-0} NYASH_AOT_MAP_KEY_MODE=${NYASH_AOT_MAP_KEY_MODE:-auto} \
NYASH_JSON_ONLY=${NYASH_JSON_ONLY:-1} \
"$NYASH_BIN" --backend vm "$_prep_hako" >"$_prep_stdout" 2>"$_prep_stderr"
_rc=$?
set -e
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:trace] AotPrep runner rc=$_rc" >&2
fi
if [ $_rc -eq 0 ] && grep -q "\[PREP_OUT_BEGIN\]" "$_prep_stdout" && grep -q "\[PREP_OUT_END\]" "$_prep_stdout"; then
awk '/\[PREP_OUT_BEGIN\]/{flag=1;next}/\[PREP_OUT_END\]/{flag=0}flag' "$_prep_stdout" > "$out_path"
[ "${HAKO_SELFHOST_TRACE:-0}" = "1" ] && echo "[provider/emit:trace] AotPrep applied (run_json)" >&2
# Optional: surface CollectionsHot trace lines for diagnostics when requested
if [ "${NYASH_AOT_CH_TRACE:-0}" = "1" ]; then
if command -v rg >/dev/null 2>&1; then
rg -n '^\[aot/collections_hot\]' "$_prep_stdout" >&2 || true
else
grep '^\[aot/collections_hot\]' "$_prep_stdout" >&2 || true
fi
fi
else
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:trace] AotPrep skipped or failed (run_json) rc=$_rc" >&2
if [ -s "$_prep_stderr" ]; then
echo "[provider/emit:trace] AotPrep stderr (tail):" >&2
tail -n 60 "$_prep_stderr" >&2 || true
fi
fi
fi
rm -f "$_prep_hako" "$_prep_stdout" "$_prep_stderr" 2>/dev/null || true
fi
echo "[OK] MIR JSON written (delegate:provider): $out_path"
return 0
}
# Replace LIMIT_PLACEHOLDER with actual limit
sed -i "s/LIMIT_PLACEHOLDER/$limit/g" "$out_path"
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[selfhost-direct:ok] Direct MIR assembly (FORCE=1), limit=$limit" >&2
fi
return 0
fi
# Builder box selection (default: hako.mir.builder)
local builder_box="${HAKO_MIR_BUILDER_BOX:-hako.mir.builder}"
local tmp_hako; tmp_hako=$(mktemp --suffix .hako)
if [ "$builder_box" = "hako.mir.builder.min" ]; then
cat >"$tmp_hako" <<'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/selfhost-first:fail:nojson]"); return 1 }
local mir_out = BuilderRunnerMinBox.run(prog_json)
if mir_out == null { print("[builder/selfhost-first:fail:emit]"); return 1 }
print("[builder/selfhost-first:ok]")
print("[MIR_OUT_BEGIN]")
print("" + mir_out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
else
cat >"$tmp_hako" <<'HCODE'
using "__BUILDER_BOX__" as MirBuilderBox
static box Main { method main(args) {
local prog_json = env.get("HAKO_BUILDER_PROGRAM_JSON")
if prog_json == null { print("[builder/selfhost-first:fail:nojson]"); return 1 }
local mir_out = MirBuilderBox.emit_from_program_json_v0(prog_json, null)
if mir_out == null { print("[builder/selfhost-first:fail:emit]"); return 1 }
print("[builder/selfhost-first:ok]")
print("[MIR_OUT_BEGIN]")
print("" + mir_out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
sed -i "s|__BUILDER_BOX__|$builder_box|g" "$tmp_hako"
fi
local tmp_stdout; tmp_stdout=$(mktemp)
trap 'rm -f "$tmp_hako" "$tmp_stdout" || true' RETURN
# Trace mode: analyze Program(JSON) before passing to builder
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
local prog_len=${#prog_json}
local loop_count=$(printf '%s' "$prog_json" | grep -o '"type":"Loop"' 2>/dev/null | wc -l | tr -d ' \n')
local cmp_count=$(printf '%s' "$prog_json" | grep -o '"type":"Compare"' 2>/dev/null | wc -l | tr -d ' \n')
local array_count=$(printf '%s' "$prog_json" | grep -o '"type":"MethodCall"' 2>/dev/null | wc -l | tr -d ' \n')
loop_count=${loop_count:-0}
cmp_count=${cmp_count:-0}
array_count=${array_count:-0}
local cwd="$(pwd)"
local toml_status="absent"
if [ -f "$ROOT/nyash.toml" ]; then
toml_status="present"
fi
echo "[builder/selfhost-first:trace] builder_box=$builder_box prog_json_len=$prog_len tokens=Loop:$loop_count,Compare:$cmp_count,MethodCall:$array_count cwd=$cwd nyash.toml=$toml_status" >&2
# Show first 200 chars of Program(JSON) for structural validation
local prog_head=$(printf '%s' "$prog_json" | head -c 200)
echo "[builder/selfhost-first:trace] prog_json_head: $prog_head..." >&2
fi
set +e
# Run from repo root to ensure nyash.toml is available for using resolution
# Capture both stdout and stderr (2>&1) instead of discarding stderr
local tmp_stderr; tmp_stderr=$(mktemp)
trap 'rm -f "$tmp_hako" "$tmp_stdout" "$tmp_stderr" || true' RETURN
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:exec] Starting builder execution..." >&2
fi
(cd "$ROOT" && \
HAKO_MIR_BUILDER_INTERNAL=1 HAKO_MIR_BUILDER_REGISTRY=1 \
HAKO_MIR_BUILDER_TRACE="${HAKO_SELFHOST_TRACE:-}" \
HAKO_MIR_BUILDER_LOOP_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_JSONFRAG:-}" \
HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG="${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-}" \
HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE="${HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE:-}" \
HAKO_MIR_BUILDER_JSONFRAG_PURIFY="${HAKO_MIR_BUILDER_JSONFRAG_PURIFY:-}" \
HAKO_MIR_BUILDER_METHODIZE="${HAKO_MIR_BUILDER_METHODIZE:-}" \
HAKO_MIR_BUILDER_NORMALIZE_TAG="${HAKO_MIR_BUILDER_NORMALIZE_TAG:-}" \
HAKO_MIR_BUILDER_DEBUG="${HAKO_MIR_BUILDER_DEBUG:-}" \
NYASH_DISABLE_PLUGINS="${NYASH_DISABLE_PLUGINS:-0}" NYASH_FILEBOX_MODE="core-ro" HAKO_PROVIDER_POLICY="safe-core-first" \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_USING_RESOLVER_FIRST=1 \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_USE_NY_COMPILER=0 HAKO_USE_NY_COMPILER=0 NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \
NYASH_MACRO_DISABLE=1 HAKO_MACRO_DISABLE=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json" \
"$NYASH_BIN" --backend vm "$tmp_hako" 2>"$tmp_stderr" | tee "$tmp_stdout" >/dev/null)
local rc=$?
set -e
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:exec] Builder execution completed with rc=$rc" >&2
fi
# Enhanced failure diagnostics with comprehensive logging
if [ $rc -ne 0 ]; then
if [ "${HAKO_SELFHOST_NO_DELEGATE:-0}" = "1" ] || [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:fail:child:rc=$rc]" >&2
echo "[builder/selfhost-first:fail:detail] First 20 lines of output:" >&2
head -n 20 "$tmp_stdout" >&2 || true
echo "[builder/selfhost-first:fail:detail] Last 80 lines of output:" >&2
tail -n 80 "$tmp_stdout" >&2 || true
if [ -s "$tmp_stderr" ]; then
echo "[builder/selfhost-first:fail:stderr] First 20 lines:" >&2
head -n 20 "$tmp_stderr" >&2 || true
echo "[builder/selfhost-first:fail:stderr] Last 40 lines:" >&2
tail -n 40 "$tmp_stderr" >&2 || true
# Pretty diagnostics for missing using modules
USING_MISSING=$(cat "$tmp_stdout" "$tmp_stderr" 2>/dev/null | grep -Eo "\[using\] not found: '[^']+'" | sort -u || true)
if [ -n "$USING_MISSING" ]; then
echo "[builder/selfhost-first:diagnose] Missing using modules detected:" >&2
echo "$USING_MISSING" >&2
echo "[builder/selfhost-first:diagnose] Hint: enable resolver-first (HAKO_USING_RESOLVER_FIRST=1) and ensure nyash.toml maps these modules." >&2
echo "[builder/selfhost-first:diagnose] Example entries (nyash.toml [modules]):" >&2
echo " \"selfhost.shared.json.core.json_canonical\" = \"lang/src/shared/json/json_canonical_box.hako\"" >&2
echo " \"selfhost.shared.common.common_imports\" = \"lang/src/shared/common/common_imports.hako\"" >&2
fi
fi
fi
# Don't return immediately - check for fallback below
fi
if [ $rc -eq 0 ] && ! grep -q "\[builder/selfhost-first:ok\]" "$tmp_stdout"; then
if [ "${HAKO_SELFHOST_NO_DELEGATE:-0}" = "1" ] || [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:fail:no-ok-marker]" >&2
echo "[builder/selfhost-first:fail:detail] First 20 lines of output:" >&2
head -n 20 "$tmp_stdout" >&2 || true
echo "[builder/selfhost-first:fail:detail] Last 80 lines of output:" >&2
tail -n 80 "$tmp_stdout" >&2 || true
fi
rc=1
fi
# Try min builder fallback if enabled and initial builder failed
if [ "${HAKO_SELFHOST_TRY_MIN:-0}" = "1" ] && [ $rc -ne 0 ] && [ "$builder_box" != "hako.mir.builder.min" ]; then
if [ "${HAKO_SELFHOST_NO_DELEGATE:-0}" = "1" ] || [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:trying-min-fallback]" >&2
fi
# Retry with min builder
HAKO_MIR_BUILDER_BOX="hako.mir.builder.min" try_selfhost_builder "$prog_json" "$out_path"
local fallback_rc=$?
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[builder/selfhost-first:min-fallback:rc=$fallback_rc]" >&2
fi
return $fallback_rc
fi
# Return original failure if no fallback or if fallback not triggered
if [ $rc -ne 0 ]; then
return 1
fi
local mir
mir=$(awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag' "$tmp_stdout")
if [ -z "$mir" ]; then return 1; fi
printf '%s' "$mir" > "$out_path"
echo "[OK] MIR JSON written (selfhost-first): $out_path"
return 0
}
# Provider-first delegate: call env.mirbuilder.emit(prog_json) and capture v1 JSON
try_provider_emit() {
local prog_json="$1" out_path="$2"
local tmp_hako; tmp_hako=$(mktemp --suffix .hako)
cat >"$tmp_hako" <<'HCODE'
static box Main { method main(args) {
local p = env.get("HAKO_BUILDER_PROGRAM_JSON")
if p == null { print("[provider/emit:nojson]"); return 1 }
local a = new ArrayBox(); a.push(p)
local out = hostbridge.extern_invoke("env.mirbuilder", "emit", a)
print("[provider/emit:ok]")
print("[MIR_OUT_BEGIN]")
print("" + out)
print("[MIR_OUT_END]")
return 0
} }
HCODE
local tmp_stdout; tmp_stdout=$(mktemp)
local tmp_stderr; tmp_stderr=$(mktemp)
trap 'rm -f "$tmp_hako" "$tmp_stdout" "$tmp_stderr" || true' RETURN
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:trace] Starting provider emit..." >&2
fi
set +e
(cd "$ROOT" && \
NYASH_DISABLE_PLUGINS="${NYASH_DISABLE_PLUGINS:-0}" NYASH_FILEBOX_MODE="core-ro" \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
HAKO_BUILDER_PROGRAM_JSON="$prog_json" \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_USING_RESOLVER_FIRST=1 \
"$NYASH_BIN" --backend vm "$tmp_hako" 2>"$tmp_stderr" | tee "$tmp_stdout" >/dev/null)
local rc=$?
set -e
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:trace] Provider execution completed with rc=$rc" >&2
fi
if [ $rc -ne 0 ] || ! grep -q "\[provider/emit:ok\]" "$tmp_stdout"; then
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:fail:rc=$rc]" >&2
if [ -s "$tmp_stderr" ]; then
echo "[provider/emit:fail:stderr] First 20 lines:" >&2
head -n 20 "$tmp_stderr" >&2 || true
echo "[provider/emit:fail:stderr] Last 40 lines:" >&2
tail -n 40 "$tmp_stderr" >&2 || true
fi
echo "[provider/emit:fail:stdout] Last 40 lines:" >&2
tail -n 40 "$tmp_stdout" >&2 || true
USING_MISSING=$(cat "$tmp_stdout" "$tmp_stderr" 2>/dev/null | grep -Eo "\[using\] not found: '[^']+'" | sort -u || true)
if [ -n "$USING_MISSING" ]; then
echo "[provider/emit:diagnose] Missing using modules detected:" >&2
echo "$USING_MISSING" >&2
echo "[provider/emit:diagnose] Hint: enable resolver-first (HAKO_USING_RESOLVER_FIRST=1) and ensure nyash.toml maps these modules." >&2
fi
fi
return 1
fi
local mir
mir=$(awk '/\[MIR_OUT_BEGIN\]/{flag=1;next}/\[MIR_OUT_END\]/{flag=0}flag' "$tmp_stdout")
if [ -z "$mir" ]; then
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[provider/emit:fail:no-mir-output]" >&2
fi
return 1
fi
# Write raw MIR JSON first
printf '%s' "$mir" > "$out_path"
# Apply AotPrep via run_json when enabled
if [ "${HAKO_APPLY_AOT_PREP:-0}" = "1" ] || [ "${NYASH_AOT_COLLECTIONS_HOT:-0}" = "1" ] || [ "${NYASH_LLVM_FAST:-0}" = "1" ] || [ "${NYASH_MIR_LOOP_HOIST:-0}" = "1" ]; then
[ "${HAKO_SELFHOST_TRACE:-0}" = "1" ] && echo "[provider/emit:trace] Applying AotPrep(run_json)..." >&2
local aot_runner; aot_runner=$(mktemp --suffix=.hako)
cat > "$aot_runner" << 'EOF'
using selfhost.llvm.ir.aot_prep as AotPrepBox
static box Main { method main(args) {
local src = env.get("HAKO_PREP_INPUT")
if src == null || src == "" { print("[prep:skip:empty]"); return 0 }
local out = AotPrepBox.run_json(src)
print("[PREP_OUT_BEGIN]")
print(out)
print("[PREP_OUT_END]")
return 0
} }
EOF
local aot_rc=0
local prep_stdout; prep_stdout=$(mktemp)
local prep_stderr; prep_stderr=$(mktemp)
set +e
HAKO_PREP_INPUT="$(cat "$out_path")" \
NYASH_FILEBOX_MODE=core-ro \
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \
NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_USING_RESOLVER_FIRST=1 \
NYASH_AOT_COLLECTIONS_HOT=${NYASH_AOT_COLLECTIONS_HOT:-0} NYASH_LLVM_FAST=${NYASH_LLVM_FAST:-0} NYASH_MIR_LOOP_HOIST=${NYASH_MIR_LOOP_HOIST:-0} NYASH_AOT_MAP_KEY_MODE=${NYASH_AOT_MAP_KEY_MODE:-auto} \
"$NYASH_BIN" --backend vm "$aot_runner" >"$prep_stdout" 2>"$prep_stderr"
aot_rc=$?
set -e
if [ $aot_rc -eq 0 ] && grep -q "\[PREP_OUT_BEGIN\]" "$prep_stdout" && grep -q "\[PREP_OUT_END\]" "$prep_stdout"; then
awk '/\[PREP_OUT_BEGIN\]/{flag=1;next}/\[PREP_OUT_END\]/{flag=0}flag' "$prep_stdout" > "$out_path"
[ "${HAKO_SELFHOST_TRACE:-0}" = "1" ] && echo "[prep:ok] AotPrep applied (run_json)" >&2
else
if [ "${HAKO_SELFHOST_TRACE:-0}" = "1" ]; then
echo "[prep:warn] AotPrep failed (rc=$aot_rc), using original MIR" >&2
if [ -s "$prep_stderr" ]; then
echo "[prep:stderr:tail]" >&2
tail -n 60 "$prep_stderr" >&2 || true
fi
fi
fi
rm -f "$aot_runner" "$prep_stdout" "$prep_stderr" 2>/dev/null || true
fi
echo "[OK] MIR JSON written (delegate:provider): $out_path"
return 0
}
# When forcing JSONFrag loop, default-enable normalize+purify (dev-only, no default changes)
if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then
export HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE="${HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE:-1}"
export HAKO_MIR_BUILDER_JSONFRAG_PURIFY="${HAKO_MIR_BUILDER_JSONFRAG_PURIFY:-1}"
fi
if [ "${HAKO_SELFHOST_BUILDER_FIRST:-0}" = "1" ]; then
if try_selfhost_builder "$PROG_JSON_OUT" "$OUT"; then
exit 0
fi
if [ "${HAKO_SELFHOST_NO_DELEGATE:-0}" = "1" ]; then
echo "[FAIL] selfhost-first failed and delegate disabled" >&2
exit 1
fi
fi
# Dev: force JsonFrag minimal loop even on provider-first path
if [ "${HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG:-0}" = "1" ]; then
# Extract limit from Program(JSON) or source file
limit=$(printf '%s' "$PROG_JSON_OUT" | grep -o '"type":"Int","value":[0-9]*' | head -1 | grep -o '[0-9]*$' || cat "$CODE_TMP" | grep -o '[0-9]\+' | head -1 || echo "10")
echo "[emit/jsonfrag] provider-force min-loop MIR (dev-only)" >&2
cat > "$OUT" <<MIRJSON
{
"functions": [{
"name": "main",
"params": [],
"locals": [],
"blocks": [
{ "id": 0, "instructions": [
{"op":"const","dst":1,"value":{"type":"i64","value":0}},
{"op":"const","dst":2,"value":{"type":"i64","value": ${limit} }},
{"op":"jump","target":1}
]},
{ "id": 1, "instructions": [
{"op":"phi","dst":6,"incoming":[[2,0],[6,2]]},
{"op":"phi","dst":3,"incoming":[[1,0],[5,2]]},
{"op":"compare","operation":"<","lhs":3,"rhs":6,"dst":4},
{"op":"branch","cond":4,"then":2,"else":3}
]},
{ "id": 2, "instructions": [
{"op":"const","dst":10,"value":{"type":"i64","value":1}},
{"op":"binop","operation":"+","lhs":3,"rhs":10,"dst":5},
{"op":"jump","target":1}
]},
{ "id": 3, "instructions": [
{"op":"ret","value":3}
]}
]
}]
}
MIRJSON
echo "[OK] MIR JSON written (provider-force-jsonfrag): $OUT"
exit 0
fi
tmp_prog="/tmp/hako_emit_prog_$$.json"
trap 'rm -f "$tmp_prog" || true' EXIT
printf '%s' "$PROG_JSON_OUT" > "$tmp_prog"
# Provider-first delegate (v1固定): env.mirbuilder.emit を使用
if try_provider_emit "$PROG_JSON_OUT" "$OUT"; then
exit 0
fi
# 最終フォールバック: 旧CLI変換環境でv1を促す
if HAKO_STAGEB_FUNC_SCAN="${HAKO_STAGEB_FUNC_SCAN:-}" \
HAKO_MIR_BUILDER_FUNCS="${HAKO_MIR_BUILDER_FUNCS:-}" \
HAKO_MIR_BUILDER_CALL_RESOLVE="${HAKO_MIR_BUILDER_CALL_RESOLVE:-}" \
NYASH_JSON_SCHEMA_V1=${NYASH_JSON_SCHEMA_V1:-1} \
NYASH_MIR_UNIFIED_CALL=${NYASH_MIR_UNIFIED_CALL:-1} \
"$NYASH_BIN" --program-json-to-mir "$OUT" --json-file "$tmp_prog" >/dev/null 2>&1; then
echo "[OK] MIR JSON written (delegate-legacy): $OUT"
exit 0
fi
echo "[FAIL] Program→MIR delegate failed (provider+legacy)" >&2
exit 1