2025-11-02 20:06:00 +09:00
|
|
|
|
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.
|
|
|
|
|
|
/// Fail‑Fast: prints stable tags and returns Err with the same message.
|
|
|
|
|
|
pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String> {
|
2025-11-03 16:09:19 +09:00
|
|
|
|
// Optional provider selection (default: ny-llvmc)
|
|
|
|
|
|
match std::env::var("HAKO_LLVM_EMIT_PROVIDER").ok().as_deref() {
|
|
|
|
|
|
Some("llvmlite") => return mir_json_to_object_llvmlite(mir_json, &opts),
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
2025-11-02 20:06:00 +09:00
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 16:09:19 +09:00
|
|
|
|
fn resolve_python3() -> Option<PathBuf> {
|
|
|
|
|
|
if let Ok(p) = which::which("python3") { return Some(p); }
|
|
|
|
|
|
if let Ok(p) = which::which("python") { return Some(p); }
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn resolve_llvmlite_harness() -> Option<PathBuf> {
|
|
|
|
|
|
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
|
|
|
|
|
let p = PathBuf::from(root).join("tools/llvmlite_harness.py");
|
|
|
|
|
|
if p.exists() { return Some(p); }
|
|
|
|
|
|
}
|
|
|
|
|
|
let p = PathBuf::from("tools/llvmlite_harness.py");
|
|
|
|
|
|
if p.exists() { return Some(p); }
|
|
|
|
|
|
// Also try repo-relative (target may run elsewhere)
|
|
|
|
|
|
let p2 = PathBuf::from("../tools/llvmlite_harness.py");
|
|
|
|
|
|
if p2.exists() { return Some(p2); }
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Compile via llvmlite harness (opt-in provider). Returns output path or tagged error.
|
|
|
|
|
|
fn mir_json_to_object_llvmlite(mir_json: &str, opts: &Opts) -> Result<PathBuf, String> {
|
|
|
|
|
|
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 py = resolve_python3().ok_or_else(|| {
|
|
|
|
|
|
let tag = String::from("[llvmemit/llvmlite/python-not-found]");
|
|
|
|
|
|
eprintln!("{}", tag);
|
|
|
|
|
|
tag
|
|
|
|
|
|
})?;
|
|
|
|
|
|
let harness = resolve_llvmlite_harness().ok_or_else(|| {
|
|
|
|
|
|
let tag = String::from("[llvmemit/llvmlite/harness-not-found] tools/llvmlite_harness.py");
|
|
|
|
|
|
eprintln!("{}", tag);
|
|
|
|
|
|
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))?;
|
|
|
|
|
|
}
|
|
|
|
|
|
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); }
|
|
|
|
|
|
|
|
|
|
|
|
// Run: python3 tools/llvmlite_harness.py --in <json> --out <out>
|
|
|
|
|
|
let status = Command::new(&py)
|
|
|
|
|
|
.arg(&harness)
|
|
|
|
|
|
.arg("--in").arg(&in_path)
|
|
|
|
|
|
.arg("--out").arg(&out_path)
|
|
|
|
|
|
.status()
|
|
|
|
|
|
.map_err(|e| format!("[llvmemit/llvmlite/spawn/error] {}", e))?;
|
|
|
|
|
|
if !status.success() {
|
|
|
|
|
|
let code = status.code().unwrap_or(1);
|
|
|
|
|
|
let tag = format!("[llvmemit/llvmlite/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)
|
|
|
|
|
|
}
|