Phase 20.34: wire Host providers via externs (env.mirbuilder.emit, env.codegen.emit_object); implement MirBuilder provider (Program→MIR JSON) and ny-llvmc wrapper; update Hako boxes (MirBuilderBox, LLVMEmitProviderBox) to delegate; adjust canaries to PASS (MirBuilder PASS, LLVM SKIP on unresolved or missing ny-llvmc).

This commit is contained in:
nyash-codex
2025-11-02 20:06:00 +09:00
parent 63f1242a57
commit 8827b8d416
8 changed files with 219 additions and 20 deletions

View File

@ -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<PathBuf>,
pub nyrt: Option<PathBuf>,
pub opt_level: Option<String>,
pub timeout_ms: Option<u64>,
}
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.
/// FailFast: prints stable tags and returns Err with the same message.
pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String> {
// 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 <json> --emit obj --out <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)
}

View File

@ -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<String, String> {
// 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)
}
}
}

View File

@ -0,0 +1,3 @@
pub mod mir_builder;
pub mod llvm_codegen;

View File

@ -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<dyn NyashBox>]) -> BidResult<Opt
}
}
/// Handle env.mirbuilder.* methods (Program(JSON v0) → MIR(JSON v0))
fn handle_mirbuilder(method_name: &str, args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
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<dyn NyashBox>)),
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<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
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<dyn NyashBox>)),
Err(_e) => Ok(None),
}
}
_ => Err(BidError::PluginError),
}
}
/// Handle env.future.await method
fn handle_future_await(args: &[Box<dyn NyashBox>]) -> BidResult<Option<Box<dyn NyashBox>>> {
if let Some(arg) = args.get(0) {