use std::env;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use cudarc::nvrtc::{CompileOptions, Ptx};
use super::GPU_LOG_TARGET;
use crate::engine::cuda_context::libnvrtc_present;
use crate::engine::knob::Knob;
static DIRECTORY: Knob<Option<PathBuf>> = Knob::new(
"PAULISTRINGS_KERNEL_CACHE",
GPU_LOG_TARGET,
|text| Some((text != "off").then(|| PathBuf::from(text))),
|| default_directory(env::var("XDG_CACHE_HOME").ok(), env::var("HOME").ok()),
);
pub(crate) fn directory() -> Option<&'static Path> {
DIRECTORY.get().as_deref()
}
fn default_directory(xdg_cache_home: Option<String>, home: Option<String>) -> Option<PathBuf> {
let base = match xdg_cache_home.filter(|xdg| !xdg.is_empty()) {
Some(xdg) => PathBuf::from(xdg),
None => PathBuf::from(home?).join(".cache"),
};
Some(base.join("paulistrings").join("kernels"))
}
fn nvrtc_version() -> (i32, i32) {
if !libnvrtc_present() {
return (0, 0);
}
let mut major = 0;
let mut minor = 0;
unsafe {
cudarc::nvrtc::sys::nvrtcVersion(&mut major, &mut minor);
}
(major, minor)
}
fn entry_name(source: &str, options: &CompileOptions) -> String {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
source.hash(&mut hasher);
options.hash(&mut hasher);
nvrtc_version().hash(&mut hasher);
env!("CARGO_PKG_VERSION").hash(&mut hasher);
format!("{:016x}.ptx", hasher.finish())
}
pub(crate) fn lookup(dir: &Path, source: &str, options: &CompileOptions) -> Option<Ptx> {
let path = dir.join(entry_name(source, options));
let text = fs::read_to_string(&path).ok()?;
if text.is_empty() {
return None;
}
Some(Ptx::from_src(text))
}
pub(crate) fn store(dir: &Path, source: &str, options: &CompileOptions, ptx_text: &str) {
if let Err(e) = fs::create_dir_all(dir) {
log::debug!(
target: GPU_LOG_TARGET,
"kernel cache directory {} unwritable, compiling without a cache: {e}",
dir.display()
);
return;
}
let path = dir.join(entry_name(source, options));
let unique =
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or_default();
let temporary = dir.join(format!(".tmp-{}-{unique}", std::process::id()));
if fs::write(&temporary, ptx_text).is_err() {
let _ = fs::remove_file(&temporary);
return;
}
if fs::rename(&temporary, &path).is_err() {
let _ = fs::remove_file(&temporary);
}
}
#[cfg(test)]
mod tests;