use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::sync::{Once, OnceLock};
use cubecl::config::cache::CacheConfig;
use cubecl::config::{CubeClRuntimeConfig, RuntimeConfig};
use etcetera::base_strategy::{BaseStrategy, choose_base_strategy};
pub const COMPILATION_CACHE_ENV: &str = "AV_DENOISE_COMPILATION_CACHE";
static CACHE_DIR: OnceLock<Option<PathBuf>> = OnceLock::new();
const CACHE_DIR_NAME: &str = "av-denoise";
const DISABLE_WORDS: [&str; 4] = ["off", "0", "false", "none"];
#[derive(Debug, thiserror::Error)]
pub enum CacheError {
#[error("CubeCL global config already initialized. Install the cache before any Denoiser::create")]
AlreadyInitialised,
#[error("cannot create the kernel cache directory {path}", path = path.display())]
Create {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CacheLocation {
Disabled,
Dir(PathBuf),
}
pub fn default_cache_dir() -> PathBuf {
let platform_cache = choose_base_strategy().ok().map(|s| s.cache_dir());
if platform_cache.is_none() {
tracing::warn!("no platform cache directory available, falling back to the temporary directory");
}
resolve_default_dir(platform_cache, std::env::temp_dir())
}
fn resolve_default_dir(platform_cache: Option<PathBuf>, temp: PathBuf) -> PathBuf {
platform_cache.unwrap_or(temp).join(CACHE_DIR_NAME)
}
pub(crate) fn resolve_cache_location(
env: Option<&OsStr>,
default: impl FnOnce() -> PathBuf,
) -> CacheLocation {
if let Some(raw) = env {
let text = raw.to_string_lossy();
let trimmed = text.trim();
if !trimmed.is_empty() {
if DISABLE_WORDS.iter().any(|w| trimmed.eq_ignore_ascii_case(w)) {
return CacheLocation::Disabled;
}
let dir = match raw.to_str() {
Some(s) => PathBuf::from(s.trim()),
None => PathBuf::from(raw),
};
return CacheLocation::Dir(dir);
}
}
CacheLocation::Dir(default())
}
pub fn install_compilation_cache_at(dir: &Path) -> Result<(), CacheError> {
if let Err(source) = std::fs::create_dir_all(dir) {
return Err(CacheError::Create {
path: dir.to_path_buf(),
source,
});
}
set_runtime_config(dir)?;
let _ = CACHE_DIR.set(Some(dir.to_path_buf()));
Ok(())
}
pub fn install_compilation_cache() -> Result<Option<PathBuf>, CacheError> {
let location = resolve_cache_location(
std::env::var_os(COMPILATION_CACHE_ENV).as_deref(),
default_cache_dir,
);
let CacheLocation::Dir(path) = location else {
return Ok(None);
};
if let Err(err) = std::fs::create_dir_all(&path) {
tracing::warn!(
?path,
%err,
"cannot create the kernel cache directory, continuing without a cache"
);
return Ok(None);
}
set_runtime_config(&path)?;
let _ = CACHE_DIR.set(Some(path.clone()));
Ok(Some(path))
}
pub fn install_compilation_cache_once() -> Option<&'static Path> {
static ONCE: Once = Once::new();
ONCE.call_once(|| match install_compilation_cache() {
Ok(Some(path)) => tracing::info!(?path, "caching compiled kernels"),
Ok(None) => tracing::info!("kernel caching is off, every run recompiles"),
Err(err) => tracing::warn!(
%err,
"something else configured CubeCL first, leaving its kernel cache alone"
),
});
compilation_cache_dir()
}
pub fn compilation_cache_dir() -> Option<&'static Path> {
CACHE_DIR.get()?.as_deref()
}
fn set_runtime_config(path: &Path) -> Result<(), CacheError> {
let mut cfg = CubeClRuntimeConfig::from_current_dir().override_from_env();
cfg.compilation.cache = Some(CacheConfig::File(path.to_path_buf()));
cfg.autotune.cache = CacheConfig::File(path.to_path_buf());
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
CubeClRuntimeConfig::set(cfg);
}))
.map_err(|_| CacheError::AlreadyInitialised)
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use super::*;
fn os(text: &str) -> OsString {
OsString::from(text)
}
fn resolve(env: Option<&str>, default: &str) -> CacheLocation {
let env = env.map(os);
let default = PathBuf::from(default);
resolve_cache_location(env.as_deref(), || default)
}
#[test]
fn with_nothing_set_the_default_wins() {
assert_eq!(
resolve(None, "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/home/u/.cache/av-denoise")),
);
}
#[test]
fn an_explicit_path_overrides_the_default() {
assert_eq!(
resolve(Some("/mnt/cache"), "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/mnt/cache")),
);
}
#[test]
fn the_disable_words_turn_caching_off() {
for word in ["off", "OFF", "Off", "0", "false", "FALSE", "none", " off "] {
assert_eq!(
resolve(Some(word), "/home/u/.cache/av-denoise"),
CacheLocation::Disabled,
"{word} should disable caching",
);
}
}
#[test]
fn a_path_containing_a_disable_word_is_still_a_path() {
assert_eq!(
resolve(Some("/tmp/offsite"), "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/tmp/offsite")),
);
}
#[test]
fn an_empty_variable_takes_the_default() {
assert_eq!(
resolve(Some(""), "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/home/u/.cache/av-denoise")),
);
assert_eq!(
resolve(Some(" "), "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/home/u/.cache/av-denoise")),
);
}
#[test]
fn a_padded_explicit_path_is_trimmed() {
assert_eq!(
resolve(Some(" /mnt/cache "), "/home/u/.cache/av-denoise"),
CacheLocation::Dir(PathBuf::from("/mnt/cache")),
);
}
#[cfg(unix)]
#[test]
fn a_non_utf8_override_is_carried_through_untrimmed() {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
let bytes = vec![b'/', b'm', b'n', b't', b'/', 0xFF, b'x'];
let env = OsString::from_vec(bytes.clone());
assert_eq!(
resolve_cache_location(Some(&env), || PathBuf::from("/home/u/.cache/av-denoise")),
CacheLocation::Dir(PathBuf::from(OsString::from_vec(bytes))),
);
}
#[test]
fn resolve_default_dir_joins_the_platform_cache_directory() {
assert_eq!(
resolve_default_dir(Some(PathBuf::from("/home/u/.cache")), PathBuf::from("/tmp"),),
PathBuf::from("/home/u/.cache/av-denoise"),
);
}
#[test]
fn resolve_default_dir_falls_back_to_the_temporary_directory() {
assert_eq!(
resolve_default_dir(None, PathBuf::from("/tmp")),
PathBuf::from("/tmp/av-denoise"),
);
}
}