use crate::emit::emit_air;
use crate::ir::{plan_chunks, Ir};
use crate::toolchain::Toolchain;
use rayon::prelude::*;
use regex::Regex;
use std::path::{Path, PathBuf};
const CHUNK_MAX: usize = 512; const CHUNK_MIN: usize = 64; const BIG_OPS: usize = 20000; const BIG_START_CHUNK: usize = 250;
fn max_stack(obj: &Path) -> i64 {
let out = match std::process::Command::new("cuobjdump").arg("-res-usage").arg(obj).output() {
Ok(o) => o,
Err(_) => return -1,
};
let text = String::from_utf8_lossy(&out.stdout);
let re = Regex::new(r"STACK:(\d+)").unwrap();
re.captures_iter(&text).filter_map(|c| c[1].parse::<i64>().ok()).max().unwrap_or(0)
}
pub fn tune_chunk(tc: &Toolchain, ir: &Ir, sym: &str, n_ops: usize, out_dir: &Path) -> anyhow::Result<Option<usize>> {
let probe = out_dir.join(format!(".probe_{sym}"));
std::fs::create_dir_all(&probe)?;
std::fs::write(probe.join("gen_common.cuh"), crate::emit::COMMON_CUH)?;
let result = tune_inner(tc, ir, sym, n_ops, &probe, out_dir);
let _ = std::fs::remove_dir_all(&probe);
result
}
fn tune_inner(
tc: &Toolchain,
ir: &Ir,
sym: &str,
n_ops: usize,
probe: &Path,
out_dir: &Path,
) -> anyhow::Result<Option<usize>> {
let mut chunk = n_ops.min(if n_ops > BIG_OPS { BIG_START_CHUNK } else { CHUNK_MAX });
while chunk >= CHUNK_MIN {
let plan = plan_chunks(ir, chunk, sym)?;
let files = emit_air(ir, &plan, sym);
let objs: Vec<PathBuf> =
files.iter().map(|(fname, _)| probe.join(fname.strip_suffix(".cu").unwrap().to_string() + ".o")).collect();
for (fname, text) in &files {
std::fs::write(probe.join(fname), text)?;
}
let results: Vec<(bool, String)> = files
.par_iter()
.zip(&objs)
.map(|((fname, _), obj)| tc.compile_tu(&probe.join(fname), obj, Some(probe)))
.collect::<anyhow::Result<Vec<_>>>()?;
if let Some((_, err)) = results.iter().find(|(ok, _)| !ok) {
let last = err.trim().lines().last().unwrap_or("");
eprintln!(" [tune] {sym} chunk={chunk} COMPILE FAILED: {last}");
return Ok(None);
}
let stacks: Vec<i64> = objs.iter().map(|o| max_stack(o)).collect();
if stacks.iter().any(|&s| s < 0) {
anyhow::bail!(
"cuobjdump could not read register usage for {sym} (not found or failed to run). \
Install CUDA binutils so the no-spill autotuner can verify STACK=0, \
or pass --chunk <N> to skip autotuning."
);
}
let st = stacks.into_iter().max().unwrap_or(0);
eprintln!(" [tune] {sym} chunk={chunk} ({} TUs) -> STACK={st}", files.len());
if st == 0 {
for obj in &objs {
let _ = std::fs::copy(obj, out_dir.join(obj.file_name().unwrap()));
}
return Ok(Some(chunk));
}
chunk /= 2;
}
Ok(None)
}