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 { // 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), _ => {} } // 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) } fn resolve_python3() -> Option { 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 { 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 { 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 --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) }