Cache directory hierarchy (#217)

* Simple module compilation cache

* Fix base64 encoding bug

* Use warn! everywhere in cache system

* Remove unused import

* Temporary workaround for long path on Windows

* Remove unused import for non-windows builds

* Cache directory hierarchy

* Fix conditional compilation for debug mode

* Minor enhancements
This commit is contained in:
Artur Jamro
2019-07-26 07:24:58 -07:00
committed by Dan Gohman
parent 165dc4944d
commit b7d86af0ec
3 changed files with 101 additions and 36 deletions

View File

@@ -0,0 +1,9 @@
use std::process::Command;
fn main() {
let git_rev = match Command::new("git").args(&["rev-parse", "HEAD"]).output() {
Ok(output) => String::from_utf8(output.stdout).unwrap(),
Err(_) => String::from("git-not-found"),
};
println!("cargo:rustc-env=GIT_REV={}", git_rev);
}

View File

@@ -5,17 +5,15 @@ use cranelift_codegen::ir;
use cranelift_codegen::isa; use cranelift_codegen::isa;
use directories::ProjectDirs; use directories::ProjectDirs;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::warn; use log::{debug, warn};
use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
use serde::ser::{self, Serialize, SerializeSeq, SerializeStruct, Serializer}; use serde::ser::{self, Serialize, SerializeSeq, SerializeStruct, Serializer};
#[cfg(windows)]
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt; use std::fmt;
use std::fs; use std::fs;
use std::io; use std::io;
#[cfg(windows)]
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::string::{String, ToString};
/// Module for configuring the cache system. /// Module for configuring the cache system.
pub mod conf { pub mod conf {
@@ -48,29 +46,47 @@ lazy_static! {
Some(proj_dirs) => { Some(proj_dirs) => {
let cache_dir = proj_dirs.cache_dir(); let cache_dir = proj_dirs.cache_dir();
// Temporary workaround for: https://github.com/rust-lang/rust/issues/32689 // Temporary workaround for: https://github.com/rust-lang/rust/issues/32689
#[cfg(windows)] if cfg!(windows) {
let mut long_path = OsString::from("\\\\?\\"); let mut long_path = OsString::new();
#[cfg(windows)] if !cache_dir.starts_with("\\\\?\\") {
let cache_dir = { long_path.push("\\\\?\\");
if cache_dir.starts_with("\\\\?\\") {
cache_dir
} }
else { long_path.push(cache_dir.as_os_str());
long_path.push(cache_dir.as_os_str()); Some(PathBuf::from(long_path))
Path::new(&long_path) }
} else {
}; Some(cache_dir.to_path_buf())
match fs::create_dir_all(cache_dir) { }
Ok(()) => (),
Err(err) => warn!("Unable to create cache directory, failed with: {}", err),
};
Some(cache_dir.to_path_buf())
} }
None => { None => {
warn!("Unable to find cache directory"); warn!("Failed to find proper cache directory location.");
None None
} }
}; };
static ref SELF_MTIME: String = {
match std::env::current_exe() {
Ok(path) => match fs::metadata(&path) {
Ok(metadata) => match metadata.modified() {
Ok(mtime) => match mtime.duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => format!("{}", duration.as_millis()),
Err(err) => format!("m{}", err.duration().as_millis()),
},
Err(_) => {
warn!("Failed to get modification time of current executable");
"no-mtime".to_string()
}
},
Err(_) => {
warn!("Failed to get metadata of current executable");
"no-mtime".to_string()
}
},
Err(_) => {
warn!("Failed to get path of current executable");
"no-mtime".to_string()
}
}
};
} }
pub struct ModuleCacheEntry { pub struct ModuleCacheEntry {
@@ -87,14 +103,33 @@ pub struct ModuleCacheData {
type ModuleCacheDataTupleType = (Compilation, Relocations, ModuleAddressMap); type ModuleCacheDataTupleType = (Compilation, Relocations, ModuleAddressMap);
impl ModuleCacheEntry { impl ModuleCacheEntry {
pub fn new(module: &Module, _isa: &dyn isa::TargetIsa, _generate_debug_info: bool) -> Self { pub fn new(
// TODO: cache directory hierarchy with isa name, compiler name & git revision, and files with flag if debug symbols are available module: &Module,
isa: &dyn isa::TargetIsa,
compiler_name: &str,
generate_debug_info: bool,
) -> Self {
let mod_cache_path = if conf::cache_enabled() { let mod_cache_path = if conf::cache_enabled() {
CACHE_DIR.clone().and_then(|p| { CACHE_DIR.clone().and_then(|p| {
module.hash.map(|hash| { module.hash.map(|hash| {
p.join(format!( let compiler_dir = if cfg!(debug_assertions) {
"mod-{}", format!(
base64::encode_config(&hash, base64::URL_SAFE_NO_PAD) // standard encoding uses '/' which can't be used for filename "{comp_name}-{comp_ver}-{comp_mtime}",
comp_name = compiler_name,
comp_ver = env!("GIT_REV"),
comp_mtime = *SELF_MTIME,
)
} else {
format!(
"{comp_name}-{comp_ver}",
comp_name = compiler_name,
comp_ver = env!("GIT_REV"),
)
};
p.join(isa.name()).join(compiler_dir).join(format!(
"mod-{mod_hash}{mod_dbg}",
mod_hash = base64::encode_config(&hash, base64::URL_SAFE_NO_PAD), // standard encoding uses '/' which can't be used for filename
mod_dbg = if generate_debug_info { ".d" } else { "" },
)) ))
}) })
}) })
@@ -131,25 +166,46 @@ impl ModuleCacheEntry {
return; return;
} }
}; };
// Optimize syscalls: first, try writing to disk. It should succeed in most cases.
// Otherwise, try creating the cache directory and retry writing to the file.
match fs::write(p, &cache_buf) { match fs::write(p, &cache_buf) {
Ok(()) => (), Ok(()) => (),
Err(err) => { Err(err) => {
warn!( debug!(
"Failed to write cached code to disk, path: {}, message: {}", "Attempting to create the cache directory, because \
failed to write cached code to disk, path: {}, message: {}",
p.display(), p.display(),
err err,
); );
match fs::remove_file(p) { let cache_dir = p.parent().unwrap();
Ok(()) => (), match fs::create_dir_all(cache_dir) {
Err(err) => { Ok(()) => match fs::write(p, &cache_buf) {
if err.kind() != io::ErrorKind::NotFound { Ok(()) => (),
Err(err) => {
warn!( warn!(
"Failed to cleanup invalid cache, path: {}, message: {}", "Failed to write cached code to disk, path: {}, message: {}",
p.display(), p.display(),
err err
); );
match fs::remove_file(p) {
Ok(()) => (),
Err(err) => {
if err.kind() != io::ErrorKind::NotFound {
warn!(
"Failed to cleanup invalid cache, path: {}, message: {}",
p.display(),
err
);
}
}
}
} }
} },
Err(err) => warn!(
"Failed to create cache directory, path: {}, message: {}",
cache_dir.display(),
err
),
} }
} }
} }

View File

@@ -124,7 +124,7 @@ impl crate::compilation::Compiler for Cranelift {
isa: &dyn isa::TargetIsa, isa: &dyn isa::TargetIsa,
generate_debug_info: bool, generate_debug_info: bool,
) -> Result<(Compilation, Relocations, ModuleAddressMap), CompileError> { ) -> Result<(Compilation, Relocations, ModuleAddressMap), CompileError> {
let cache_entry = ModuleCacheEntry::new(module, isa, generate_debug_info); let cache_entry = ModuleCacheEntry::new(module, isa, "cranelift", generate_debug_info);
let data = match cache_entry.get_data() { let data = match cache_entry.get_data() {
Some(data) => data, Some(data) => data,