use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Arc, Mutex};
use fusevm::{Chunk, Op, VMResult, VM};
use crate::compiler::ext_wide;
use crate::runtime::{to_tcl_string, Hooks, Outcome};
pub fn compile_object(src: &str, out: &Path) -> Result<(), String> {
let chunk = lower(src)?;
fusevm::aot::compile_object(&chunk, out)
}
fn lower(src: &str) -> Result<Chunk, String> {
let chunk = crate::runtime::compile(src)?;
if let Some(what) = needs_the_driver(&chunk) {
return Err(format!(
"ahead-of-time compilation of a script using {what} is not supported: it needs the \
driver that only the interpreter has"
));
}
Ok(chunk)
}
fn needs_the_driver(chunk: &Chunk) -> Option<&'static str> {
chunk.ops.iter().find_map(|op| match op {
Op::ExtendedWide(id, _) if *id == ext_wide::CATCH => Some("\"catch\""),
Op::Extended(id, _) if crate::coro::is_op(*id) => Some("a coroutine"),
_ => None,
})
}
pub fn run_native(src: &str) -> Result<Outcome, String> {
let chunk = lower(src)?;
let output = Arc::new(Mutex::new(String::new()));
let hooks: Arc<Mutex<Option<Hooks>>> = Arc::new(Mutex::new(None));
let sink = Arc::clone(&output);
let cell = Arc::clone(&hooks);
let outcome = fusevm::aot::run_chunk_native(&chunk, move |vm: &mut VM| {
*cell.lock().expect("hooks lock") =
Some(crate::runtime::install_hooks_capturing(vm, Arc::clone(&sink)));
})?;
if let Some(msg) = hooks
.lock()
.expect("hooks lock")
.take()
.and_then(|h| h.take_error())
{
return Err(msg);
}
let output = output.lock().expect("output lock").clone();
match outcome {
VMResult::Ok(v) => Ok(Outcome {
result: to_tcl_string(&v),
output,
}),
VMResult::Halted => Ok(Outcome {
result: String::new(),
output,
}),
VMResult::Error(e) => Err(e),
}
}
pub fn compile_executable(src: &str, out: &Path) -> Result<(), String> {
let stem = format!("tclrs_aot_{}", std::process::id());
let tmp = std::env::temp_dir();
let obj = tmp.join(format!("{stem}.o"));
let main_c = tmp.join(format!("{stem}.c"));
compile_object(src, &obj)?;
std::fs::write(
&main_c,
"extern long fusevm_aot_run_embedded(void);\n\
extern int tclrs_aot_report_error(void);\n\
int main(void) {\n\
\tlong status = fusevm_aot_run_embedded();\n\
\tif (tclrs_aot_report_error()) return 1;\n\
\treturn (int)status;\n\
}\n",
)
.map_err(|e| format!("aot: write {}: {e}", main_c.display()))?;
let lib = staticlib_path()?;
let mut cmd = Command::new("cc");
cmd.arg(&main_c).arg(&obj).arg(&lib).arg("-o").arg(out);
if cfg!(target_os = "macos") {
cmd.args(["-framework", "CoreFoundation", "-liconv"]);
} else {
cmd.args(["-lpthread", "-ldl", "-lm"]);
}
let status = cmd.status().map_err(|e| format!("aot: cc: {e}"))?;
let _ = std::fs::remove_file(&main_c);
let _ = std::fs::remove_file(&obj);
if !status.success() {
return Err(format!("aot: link failed (cc exit {:?})", status.code()));
}
Ok(())
}
fn staticlib_path() -> Result<PathBuf, String> {
if let Ok(p) = std::env::var("TCLRS_STATICLIB") {
return Ok(PathBuf::from(p));
}
let exe = std::env::current_exe().map_err(|e| format!("aot: current exe: {e}"))?;
let dir = exe
.parent()
.ok_or("aot: no directory for the running binary")?;
for candidate in [
dir.join("libtclrs.a"),
dir.join("deps").join("libtclrs.a"),
dir.join("..").join("libtclrs.a"),
] {
if candidate.exists() {
return Ok(candidate);
}
}
Err(format!(
"aot: libtclrs.a not found beside {}; build the staticlib or set TCLRS_STATICLIB",
exe.display()
))
}