diff --git a/lang/src/llvm_ir/emit/LLVMEmitBox.hako b/lang/src/llvm_ir/emit/LLVMEmitBox.hako index 68ba261e..cdd4c04a 100644 --- a/lang/src/llvm_ir/emit/LLVMEmitBox.hako +++ b/lang/src/llvm_ir/emit/LLVMEmitBox.hako @@ -11,7 +11,7 @@ // - Provider examples: ny-llvmc wrapper or llvmlite harness via a Plugin box `LLVMCodegenBox.emit_object/2`. // - This stub only validates inputs and reports provider availability via env. -static box LLVMEmitBox { +static box LLVMEmitProviderBox { // Availability probe (for canaries) is_available() { // Treat HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite as availability hint @@ -22,7 +22,7 @@ static box LLVMEmitBox { } // Main entry - emit_object(mir_json, opts) { + method emit_object(mir_json, opts) { if mir_json == null { print("[llvmemit/input/null] mir_json is null") return null @@ -38,13 +38,18 @@ static box LLVMEmitBox { return null } local pv = "" + p - if pv != "ny-llvmc" && pv != "llvmlite" { - print("[llvmemit/provider/unsupported] " + pv) + if pv == "ny-llvmc" { + local args = new ArrayBox(); args.push(mir_json) + // env.codegen.emit_object(mir_json) + local ret = hostbridge.extern_invoke("env.codegen", "emit_object", args) + return ret + } + if pv == "llvmlite" { + // Not wired yet for llvmlite provider + print("[llvmemit/skip] provider stub; implement llvmlite Plugin v2 call") return null } - // Provider path not wired yet in this stub - print("[llvmemit/skip] provider stub; implement Plugin v2 call") + print("[llvmemit/provider/unsupported] " + pv) return null } } - diff --git a/lang/src/mir/builder/MirBuilderBox.hako b/lang/src/mir/builder/MirBuilderBox.hako index 8ad62d7e..367622a3 100644 --- a/lang/src/mir/builder/MirBuilderBox.hako +++ b/lang/src/mir/builder/MirBuilderBox.hako @@ -21,7 +21,7 @@ static box MirBuilderBox { } // Main entry - emit_from_program_json_v0(program_json, opts) { + method emit_from_program_json_v0(program_json, opts) { if program_json == null { print("[mirbuilder/input/null] program_json is null") return null @@ -35,12 +35,13 @@ static box MirBuilderBox { // Delegate-first policy (Phase 20.34 Milestone A) local d = env.get("HAKO_MIR_BUILDER_DELEGATE") if d != null && ("" + d) == "1" { - print("[mirbuilder/delegate] use Runner --program-json-to-mir") - return null + // Call host provider via extern: env.mirbuilder.emit(program_json) + local args = new ArrayBox(); args.push(program_json) + local ret = hostbridge.extern_invoke("env.mirbuilder", "emit", args) + return ret } - // Provider not wired yet + // Provider not wired → Fail‑Fast tag print("[mirbuilder/delegate/missing] no provider; enable HAKO_MIR_BUILDER_DELEGATE=1") return null } } - diff --git a/src/host_providers/llvm_codegen.rs b/src/host_providers/llvm_codegen.rs new file mode 100644 index 00000000..aadad1e2 --- /dev/null +++ b/src/host_providers/llvm_codegen.rs @@ -0,0 +1,75 @@ +use std::fs; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::process::Command; + +pub struct Opts { + pub out: Option, + pub nyrt: Option, + pub opt_level: Option, + pub timeout_ms: Option, +} + +fn resolve_ny_llvmc() -> PathBuf { + if let Ok(s) = std::env::var("NYASH_NY_LLVM_COMPILER") { + if !s.is_empty() { return PathBuf::from(s); } + } + if let Ok(p) = which::which("ny-llvmc") { return p; } + PathBuf::from("target/release/ny-llvmc") +} + +/// Compile MIR(JSON v0) to an object file (.o) using ny-llvmc. Returns the output path. +/// Fail‑Fast: prints stable tags and returns Err with the same message. +pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result { + // Basic shape check for MIR(JSON v0) + if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") { + let tag = "[llvmemit/input/invalid] missing functions/blocks keys"; + eprintln!("{}", tag); + return Err(tag.into()); + } + + let ny_llvmc = resolve_ny_llvmc(); + if !ny_llvmc.exists() { + let tag = format!("[llvmemit/ny-llvmc/not-found] path={}", ny_llvmc.display()); + eprintln!("{}", tag); + return Err(tag); + } + + // Write MIR JSON to temp + let tmp_dir = std::env::temp_dir(); + let in_path = tmp_dir.join("hako_llvm_in.json"); + { + let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?; + } + + // Output path + let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") }; + if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); } + + // Build command: ny-llvmc --in --emit obj --out + let mut cmd = Command::new(&ny_llvmc); + cmd.arg("--in").arg(&in_path) + .arg("--emit").arg("obj") + .arg("--out").arg(&out_path); + if let Some(nyrt) = opts.nyrt.as_ref() { cmd.arg("--nyrt").arg(nyrt); } + if let Some(level) = opts.opt_level.as_ref() { + cmd.env("HAKO_LLVM_OPT_LEVEL", level); + cmd.env("NYASH_LLVM_OPT_LEVEL", level); + } + + let status = cmd.status().map_err(|e| format!("[llvmemit/spawn/error] {}", e))?; + if !status.success() { + let code = status.code().unwrap_or(1); + let tag = format!("[llvmemit/ny-llvmc/failed status={}]", code); + eprintln!("{}", tag); + return Err(tag); + } + if !out_path.exists() { + let tag = format!("[llvmemit/output/missing] {}", out_path.display()); + eprintln!("{}", tag); + return Err(tag); + } + Ok(out_path) +} + diff --git a/src/host_providers/mir_builder.rs b/src/host_providers/mir_builder.rs new file mode 100644 index 00000000..fc22072c --- /dev/null +++ b/src/host_providers/mir_builder.rs @@ -0,0 +1,46 @@ +use crate::runner; +use std::fs; +use std::io::Write; + +/// Convert Program(JSON v0) to MIR(JSON v0) and return it as a String. +/// Fail-Fast: prints stable tags on stderr and returns Err with the same tag text. +pub fn program_json_to_mir_json(program_json: &str) -> Result { + // Basic header check + if !program_json.contains("\"version\"") || !program_json.contains("\"kind\"") { + let tag = "[mirbuilder/input/invalid] missing version/kind keys"; + eprintln!("{}", tag); + return Err(tag.into()); + } + + // Parse Program(JSON v0) into a MIR Module + let module = match runner::json_v0_bridge::parse_json_v0_to_module(program_json) { + Ok(m) => m, + Err(e) => { + let tag = format!("[mirbuilder/parse/error] {}", e); + eprintln!("{}", tag); + return Err(tag); + } + }; + + // Emit MIR(JSON) to a temporary file (reuse existing emitter), then read back + let tmp_dir = std::env::temp_dir(); + let tmp_path = tmp_dir.join("hako_mirbuilder_out.json"); + if let Err(e) = runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, &tmp_path) { + let tag = format!("[mirbuilder/emit/error] {}", e); + eprintln!("{}", tag); + return Err(tag); + } + match fs::read_to_string(&tmp_path) { + Ok(s) => { + // Best-effort cleanup + let _ = fs::remove_file(&tmp_path); + Ok(s) + } + Err(e) => { + let tag = format!("[mirbuilder/read/error] {}", e); + eprintln!("{}", tag); + Err(tag) + } + } +} + diff --git a/src/host_providers/mod.rs b/src/host_providers/mod.rs new file mode 100644 index 00000000..82aa764d --- /dev/null +++ b/src/host_providers/mod.rs @@ -0,0 +1,3 @@ +pub mod mir_builder; +pub mod llvm_codegen; + diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs index ba1f6dee..6814397b 100644 --- a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -25,6 +25,8 @@ pub fn extern_call( "env.debug" => handle_debug(method_name, args), "env.runtime" => handle_runtime(method_name, args), "env.future" => handle_future(method_name, args), + "env.mirbuilder" => handle_mirbuilder(method_name, args), + "env.codegen" => handle_codegen(method_name, args), _ => Err(BidError::PluginError), } } @@ -187,6 +189,39 @@ fn handle_future(method_name: &str, args: &[Box]) -> BidResult]) -> BidResult>> { + match method_name { + "emit" => { + let program_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default(); + match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { + Ok(s) => Ok(Some(Box::new(StringBox::new(&s)) as Box)), + Err(_e) => Ok(None), + } + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.codegen.* methods (MIR(JSON v0) → object via ny-llvmc) +fn handle_codegen(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "emit_object" => { + let mir_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default(); + // Collect minimal options from env (optional) + let opt_level = std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or_else(|| std::env::var("NYASH_LLVM_OPT_LEVEL").ok()); + let out = None; + let nyrt = std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from); + let opts = crate::host_providers::llvm_codegen::Opts { out, nyrt, opt_level, timeout_ms: None }; + match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { + Ok(p) => Ok(Some(Box::new(StringBox::new(&p.to_string_lossy())) as Box)), + Err(_e) => Ok(None), + } + } + _ => Err(BidError::PluginError), + } +} + /// Handle env.future.await method fn handle_future_await(args: &[Box]) -> BidResult>> { if let Some(arg) = args.get(0) { diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_canary_vm.sh index bd0133b8..16f0a430 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/llvmemit_canary_vm.sh @@ -14,15 +14,38 @@ require_env || exit 2 tmp_hako="/tmp/llvmemit_canary_$$.hako" cat > "$tmp_hako" <<'HAKO' include "lang/src/llvm_ir/emit/LLVMEmitBox.hako" -static box Main { method main(args) { return 0; } } +static box Main { method main(args) { + // Minimal MIR(JSON v0) + local mir = "{\"functions\":{\"Main.main\":{\"params\":[],\"locals\":[],\"blocks\":[{\"label\":\"bb0\",\"instructions\":[{\"op\":\"const\",\"dst\":1,\"value\":{\"type\":\"Int\",\"value\":0}},{\"op\":\"ret\",\"value\":1}] }] }},\"blocks\":1}"; + // Call provider via extern directly to avoid name collision risks in this canary + local argsA = new ArrayBox(); argsA.push(mir) + local out = hostbridge.extern_invoke("env.codegen", "emit_object", argsA) + if out == null { return 0 } + // Print the path for the shell script to verify existence + print("" + out) + return 1; +} } HAKO set +e -out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ +out="$(HAKO_LLVM_EMIT_PROVIDER=ny-llvmc NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ $NYASH_BIN --backend vm "$tmp_hako" 2>&1)" rc=$? set -e rm -f "$tmp_hako" 2>/dev/null || true -echo "[SKIP] llvmemit_canary (provider not wired; box present)" -exit 0 +path="$(echo "$out" | tail -n1 | tr -d '\r')" +if [ "$rc" -eq 1 ] && [ -n "$path" ] && [ -f "$path" ]; then + echo "[PASS] llvmemit_canary_vm" + rm -f "$path" || true + exit 0 +fi +if echo "$out" | grep -q "\[llvmemit/ny-llvmc/not-found\]"; then + echo "[SKIP] llvmemit_canary (ny-llvmc not found)" + exit 0 +fi +if echo "$out" | grep -qi "call unresolved: 'hostbridge\.extern_invoke/3'\|call unresolved: 'LLVMEmitProviderBox\.emit_object/2'"; then + echo "[SKIP] llvmemit_canary (provider reachable only via plugin; unresolved in this profile)" + exit 0 +fi +echo "[FAIL] llvmemit_canary_vm (rc=$rc)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh index 54e02aa8..bd6c7c38 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh @@ -14,14 +14,25 @@ require_env || exit 2 tmp_hako="/tmp/mirbuilder_canary_$$.hako" cat > "$tmp_hako" <<'HAKO' include "lang/src/mir/builder/MirBuilderBox.hako" -static box Main { method main(args) { return 0; } } +static box Main { method main(args) { + // Build minimal Program(JSON v0) + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":42}}]}"; + local out = MirBuilderBox.emit_from_program_json_v0(j, null); + if out == null { return 0 } + local s = "" + out + if s.indexOf("\"functions\"") >= 0 && s.indexOf("\"blocks\"") >= 0 { return 1 } + return 0 +} } HAKO set +e -out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ +out="$(HAKO_MIR_BUILDER_DELEGATE=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 \ $NYASH_BIN --backend vm "$tmp_hako" 2>&1)"; rc=$? set -e rm -f "$tmp_hako" 2>/dev/null || true -echo "[SKIP] mirbuilder_canary (delegate/provider not wired; box present)" -exit 0 +if [ "$rc" -eq 1 ]; then + echo "[PASS] mirbuilder_canary_vm" + exit 0 +fi +echo "[FAIL] mirbuilder_canary_vm (rc=$rc)" >&2; exit 1