18a9b1a90SBenjamin Bouvier //! This module provides a set of primitives that allow implementing an incremental cache on top of 28a9b1a90SBenjamin Bouvier //! Cranelift, making it possible to reuse previous compiled artifacts for functions that have been 38a9b1a90SBenjamin Bouvier //! compiled previously. 48a9b1a90SBenjamin Bouvier //! 58a9b1a90SBenjamin Bouvier //! This set of operation is experimental and can be enabled using the Cargo feature 68a9b1a90SBenjamin Bouvier //! `incremental-cache`. 78a9b1a90SBenjamin Bouvier //! 88a9b1a90SBenjamin Bouvier //! This can bring speedups in different cases: change-code-and-immediately-recompile iterations 98a9b1a90SBenjamin Bouvier //! get faster, modules sharing lots of code can reuse each other's artifacts, etc. 108a9b1a90SBenjamin Bouvier //! 118a9b1a90SBenjamin Bouvier //! The three main primitives are the following: 128a9b1a90SBenjamin Bouvier //! - `compute_cache_key` is used to compute the cache key associated to a `Function`. This is 138a9b1a90SBenjamin Bouvier //! basically the content of the function, modulo a few things the caching system is resilient to. 148a9b1a90SBenjamin Bouvier //! - `serialize_compiled` is used to serialize the result of a compilation, so it can be reused 158a9b1a90SBenjamin Bouvier //! later on by... 168a9b1a90SBenjamin Bouvier //! - `try_finish_recompile`, which reads binary blobs serialized with `serialize_compiled`, 178a9b1a90SBenjamin Bouvier //! re-creating the compilation artifact from those. 188a9b1a90SBenjamin Bouvier //! 198a9b1a90SBenjamin Bouvier //! The `CacheStore` trait and `Context::compile_with_cache` method are provided as 208a9b1a90SBenjamin Bouvier //! high-level, easy-to-use facilities to make use of that cache, and show an example of how to use 218a9b1a90SBenjamin Bouvier //! the above three primitives to form a full incremental caching system. 228a9b1a90SBenjamin Bouvier 238a9b1a90SBenjamin Bouvier use core::fmt; 248a9b1a90SBenjamin Bouvier 258a9b1a90SBenjamin Bouvier use crate::alloc::string::String; 268a9b1a90SBenjamin Bouvier use crate::alloc::vec::Vec; 278a9b1a90SBenjamin Bouvier use crate::ir::function::{FunctionStencil, VersionMarker}; 288a9b1a90SBenjamin Bouvier use crate::ir::Function; 298a9b1a90SBenjamin Bouvier use crate::machinst::{CompiledCode, CompiledCodeStencil}; 308a9b1a90SBenjamin Bouvier use crate::result::CompileResult; 318a9b1a90SBenjamin Bouvier use crate::{isa::TargetIsa, timing}; 328a9b1a90SBenjamin Bouvier use crate::{trace, CompileError, Context}; 338a9b1a90SBenjamin Bouvier use alloc::borrow::{Cow, ToOwned as _}; 348a9b1a90SBenjamin Bouvier use alloc::string::ToString as _; 358a9b1a90SBenjamin Bouvier 368a9b1a90SBenjamin Bouvier impl Context { 378a9b1a90SBenjamin Bouvier /// Compile the function, as in `compile`, but tries to reuse compiled artifacts from former 388a9b1a90SBenjamin Bouvier /// compilations using the provided cache store. 398a9b1a90SBenjamin Bouvier pub fn compile_with_cache( 408a9b1a90SBenjamin Bouvier &mut self, 418a9b1a90SBenjamin Bouvier isa: &dyn TargetIsa, 428a9b1a90SBenjamin Bouvier cache_store: &mut dyn CacheKvStore, 438a9b1a90SBenjamin Bouvier ) -> CompileResult<(&CompiledCode, bool)> { 448a9b1a90SBenjamin Bouvier let cache_key_hash = { 458a9b1a90SBenjamin Bouvier let _tt = timing::try_incremental_cache(); 468a9b1a90SBenjamin Bouvier 478a9b1a90SBenjamin Bouvier let cache_key_hash = compute_cache_key(isa, &mut self.func); 488a9b1a90SBenjamin Bouvier 498a9b1a90SBenjamin Bouvier if let Some(blob) = cache_store.get(&cache_key_hash.0) { 508a9b1a90SBenjamin Bouvier match try_finish_recompile(&self.func, &blob) { 518a9b1a90SBenjamin Bouvier Ok(compiled_code) => { 528a9b1a90SBenjamin Bouvier let info = compiled_code.code_info(); 538a9b1a90SBenjamin Bouvier 548a9b1a90SBenjamin Bouvier if isa.flags().enable_incremental_compilation_cache_checks() { 558a9b1a90SBenjamin Bouvier let actual_result = self.compile(isa)?; 568a9b1a90SBenjamin Bouvier assert_eq!(*actual_result, compiled_code); 578a9b1a90SBenjamin Bouvier assert_eq!(actual_result.code_info(), info); 588a9b1a90SBenjamin Bouvier // no need to set `compiled_code` here, it's set by `compile()`. 598a9b1a90SBenjamin Bouvier return Ok((actual_result, true)); 608a9b1a90SBenjamin Bouvier } 618a9b1a90SBenjamin Bouvier 628a9b1a90SBenjamin Bouvier let compiled_code = self.compiled_code.insert(compiled_code); 638a9b1a90SBenjamin Bouvier return Ok((compiled_code, true)); 648a9b1a90SBenjamin Bouvier } 658a9b1a90SBenjamin Bouvier Err(err) => { 668a9b1a90SBenjamin Bouvier trace!("error when finishing recompilation: {err}"); 678a9b1a90SBenjamin Bouvier } 688a9b1a90SBenjamin Bouvier } 698a9b1a90SBenjamin Bouvier } 708a9b1a90SBenjamin Bouvier 718a9b1a90SBenjamin Bouvier cache_key_hash 728a9b1a90SBenjamin Bouvier }; 738a9b1a90SBenjamin Bouvier 748a9b1a90SBenjamin Bouvier let stencil = self.compile_stencil(isa).map_err(|err| CompileError { 758a9b1a90SBenjamin Bouvier inner: err, 768a9b1a90SBenjamin Bouvier func: &self.func, 778a9b1a90SBenjamin Bouvier })?; 788a9b1a90SBenjamin Bouvier 798a9b1a90SBenjamin Bouvier let stencil = { 808a9b1a90SBenjamin Bouvier let _tt = timing::store_incremental_cache(); 818a9b1a90SBenjamin Bouvier let (stencil, res) = serialize_compiled(stencil); 828a9b1a90SBenjamin Bouvier if let Ok(blob) = res { 838a9b1a90SBenjamin Bouvier cache_store.insert(&cache_key_hash.0, blob); 848a9b1a90SBenjamin Bouvier } 858a9b1a90SBenjamin Bouvier stencil 868a9b1a90SBenjamin Bouvier }; 878a9b1a90SBenjamin Bouvier 888a9b1a90SBenjamin Bouvier let compiled_code = self 898a9b1a90SBenjamin Bouvier .compiled_code 908a9b1a90SBenjamin Bouvier .insert(stencil.apply_params(&self.func.params)); 918a9b1a90SBenjamin Bouvier 928a9b1a90SBenjamin Bouvier Ok((compiled_code, false)) 938a9b1a90SBenjamin Bouvier } 948a9b1a90SBenjamin Bouvier } 958a9b1a90SBenjamin Bouvier 968a9b1a90SBenjamin Bouvier /// Backing storage for an incremental compilation cache, when enabled. 978a9b1a90SBenjamin Bouvier pub trait CacheKvStore { 988a9b1a90SBenjamin Bouvier /// Given a cache key hash, retrieves the associated opaque serialized data. 998a9b1a90SBenjamin Bouvier fn get(&self, key: &[u8]) -> Option<Cow<[u8]>>; 1008a9b1a90SBenjamin Bouvier 1018a9b1a90SBenjamin Bouvier /// Given a new cache key and a serialized blob obtained from `serialize_compiled`, stores it 1028a9b1a90SBenjamin Bouvier /// in the cache store. 1038a9b1a90SBenjamin Bouvier fn insert(&mut self, key: &[u8], val: Vec<u8>); 1048a9b1a90SBenjamin Bouvier } 1058a9b1a90SBenjamin Bouvier 1068a9b1a90SBenjamin Bouvier /// Hashed `CachedKey`, to use as an identifier when looking up whether a function has already been 1078a9b1a90SBenjamin Bouvier /// compiled or not. 1088a9b1a90SBenjamin Bouvier #[derive(Clone, Hash, PartialEq, Eq)] 1098a9b1a90SBenjamin Bouvier pub struct CacheKeyHash([u8; 32]); 1108a9b1a90SBenjamin Bouvier 1118a9b1a90SBenjamin Bouvier impl std::fmt::Display for CacheKeyHash { 1128a9b1a90SBenjamin Bouvier fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1138a9b1a90SBenjamin Bouvier write!(f, "CacheKeyHash:{:?}", self.0) 1148a9b1a90SBenjamin Bouvier } 1158a9b1a90SBenjamin Bouvier } 1168a9b1a90SBenjamin Bouvier 1178a9b1a90SBenjamin Bouvier #[derive(serde::Serialize, serde::Deserialize)] 1188a9b1a90SBenjamin Bouvier struct CachedFunc { 119*fc3c5d24Sbjorn3 // Note: The version marker must be first to ensure deserialization stops in case of a version 120*fc3c5d24Sbjorn3 // mismatch before attempting to deserialize the actual compiled code. 1218a9b1a90SBenjamin Bouvier version_marker: VersionMarker, 122*fc3c5d24Sbjorn3 stencil: CompiledCodeStencil, 1238a9b1a90SBenjamin Bouvier } 1248a9b1a90SBenjamin Bouvier 1258a9b1a90SBenjamin Bouvier /// Key for caching a single function's compilation. 1268a9b1a90SBenjamin Bouvier /// 1278a9b1a90SBenjamin Bouvier /// If two functions get the same `CacheKey`, then we can reuse the compiled artifacts, modulo some 1288a9b1a90SBenjamin Bouvier /// fixups. 1298a9b1a90SBenjamin Bouvier /// 1308a9b1a90SBenjamin Bouvier /// Note: the key will be invalidated across different versions of cranelift, as the 1318a9b1a90SBenjamin Bouvier /// `FunctionStencil` contains a `VersionMarker` itself. 1328a9b1a90SBenjamin Bouvier #[derive(Hash)] 1338a9b1a90SBenjamin Bouvier struct CacheKey<'a> { 1348a9b1a90SBenjamin Bouvier stencil: &'a FunctionStencil, 1358a9b1a90SBenjamin Bouvier parameters: CompileParameters, 1368a9b1a90SBenjamin Bouvier } 1378a9b1a90SBenjamin Bouvier 1388a9b1a90SBenjamin Bouvier #[derive(Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)] 1398a9b1a90SBenjamin Bouvier struct CompileParameters { 1408a9b1a90SBenjamin Bouvier isa: String, 1418a9b1a90SBenjamin Bouvier triple: String, 1428a9b1a90SBenjamin Bouvier flags: String, 1438a9b1a90SBenjamin Bouvier isa_flags: Vec<String>, 1448a9b1a90SBenjamin Bouvier } 1458a9b1a90SBenjamin Bouvier 1468a9b1a90SBenjamin Bouvier impl CompileParameters { 1478a9b1a90SBenjamin Bouvier fn from_isa(isa: &dyn TargetIsa) -> Self { 1488a9b1a90SBenjamin Bouvier Self { 1498a9b1a90SBenjamin Bouvier isa: isa.name().to_owned(), 1508a9b1a90SBenjamin Bouvier triple: isa.triple().to_string(), 1518a9b1a90SBenjamin Bouvier flags: isa.flags().to_string(), 1528a9b1a90SBenjamin Bouvier isa_flags: isa 1538a9b1a90SBenjamin Bouvier .isa_flags() 1548a9b1a90SBenjamin Bouvier .into_iter() 1558a9b1a90SBenjamin Bouvier .map(|v| v.value_string()) 1568a9b1a90SBenjamin Bouvier .collect(), 1578a9b1a90SBenjamin Bouvier } 1588a9b1a90SBenjamin Bouvier } 1598a9b1a90SBenjamin Bouvier } 1608a9b1a90SBenjamin Bouvier 1618a9b1a90SBenjamin Bouvier impl<'a> CacheKey<'a> { 1628a9b1a90SBenjamin Bouvier /// Creates a new cache store key for a function. 1638a9b1a90SBenjamin Bouvier /// 1648a9b1a90SBenjamin Bouvier /// This is a bit expensive to compute, so it should be cached and reused as much as possible. 1658a9b1a90SBenjamin Bouvier fn new(isa: &dyn TargetIsa, f: &'a mut Function) -> Self { 1668a9b1a90SBenjamin Bouvier // Make sure the blocks and instructions are sequenced the same way as we might 1678a9b1a90SBenjamin Bouvier // have serialized them earlier. This is the symmetric of what's done in 1688a9b1a90SBenjamin Bouvier // `try_load`. 1692c40c267Sbjorn3 let mut block = f.stencil.layout.entry_block().expect("Missing entry block"); 1702c40c267Sbjorn3 loop { 1712c40c267Sbjorn3 f.stencil.layout.full_block_renumber(block); 1722c40c267Sbjorn3 if let Some(next_block) = f.stencil.layout.next_block(block) { 1732c40c267Sbjorn3 block = next_block; 1742c40c267Sbjorn3 } else { 1752c40c267Sbjorn3 break; 1762c40c267Sbjorn3 } 1772c40c267Sbjorn3 } 1788a9b1a90SBenjamin Bouvier CacheKey { 1798a9b1a90SBenjamin Bouvier stencil: &f.stencil, 1808a9b1a90SBenjamin Bouvier parameters: CompileParameters::from_isa(isa), 1818a9b1a90SBenjamin Bouvier } 1828a9b1a90SBenjamin Bouvier } 1838a9b1a90SBenjamin Bouvier } 1848a9b1a90SBenjamin Bouvier 1858a9b1a90SBenjamin Bouvier /// Compute a cache key, and hash it on your behalf. 1868a9b1a90SBenjamin Bouvier /// 1878a9b1a90SBenjamin Bouvier /// Since computing the `CacheKey` is a bit expensive, it should be done as least as possible. 1888a9b1a90SBenjamin Bouvier pub fn compute_cache_key(isa: &dyn TargetIsa, func: &mut Function) -> CacheKeyHash { 1898a9b1a90SBenjamin Bouvier use core::hash::{Hash as _, Hasher}; 1908a9b1a90SBenjamin Bouvier use sha2::Digest as _; 1918a9b1a90SBenjamin Bouvier 1928a9b1a90SBenjamin Bouvier struct Sha256Hasher(sha2::Sha256); 1938a9b1a90SBenjamin Bouvier 1948a9b1a90SBenjamin Bouvier impl Hasher for Sha256Hasher { 1958a9b1a90SBenjamin Bouvier fn finish(&self) -> u64 { 1968a9b1a90SBenjamin Bouvier panic!("Sha256Hasher doesn't support finish!"); 1978a9b1a90SBenjamin Bouvier } 1988a9b1a90SBenjamin Bouvier fn write(&mut self, bytes: &[u8]) { 1998a9b1a90SBenjamin Bouvier self.0.update(bytes); 2008a9b1a90SBenjamin Bouvier } 2018a9b1a90SBenjamin Bouvier } 2028a9b1a90SBenjamin Bouvier 2038a9b1a90SBenjamin Bouvier let cache_key = CacheKey::new(isa, func); 2048a9b1a90SBenjamin Bouvier 2058a9b1a90SBenjamin Bouvier let mut hasher = Sha256Hasher(sha2::Sha256::new()); 2068a9b1a90SBenjamin Bouvier cache_key.hash(&mut hasher); 2078a9b1a90SBenjamin Bouvier let hash: [u8; 32] = hasher.0.finalize().into(); 2088a9b1a90SBenjamin Bouvier 2098a9b1a90SBenjamin Bouvier CacheKeyHash(hash) 2108a9b1a90SBenjamin Bouvier } 2118a9b1a90SBenjamin Bouvier 2128a9b1a90SBenjamin Bouvier /// Given a function that's been successfully compiled, serialize it to a blob that the caller may 2138a9b1a90SBenjamin Bouvier /// store somewhere for future use by `try_finish_recompile`. 2148a9b1a90SBenjamin Bouvier /// 2158a9b1a90SBenjamin Bouvier /// As this function requires ownership on the `CompiledCodeStencil`, it gives it back at the end 2168a9b1a90SBenjamin Bouvier /// of the function call. The value is left untouched. 2178a9b1a90SBenjamin Bouvier pub fn serialize_compiled( 2188a9b1a90SBenjamin Bouvier result: CompiledCodeStencil, 2198a9b1a90SBenjamin Bouvier ) -> (CompiledCodeStencil, Result<Vec<u8>, bincode::Error>) { 2208a9b1a90SBenjamin Bouvier let cached = CachedFunc { 2218a9b1a90SBenjamin Bouvier version_marker: VersionMarker, 222*fc3c5d24Sbjorn3 stencil: result, 2238a9b1a90SBenjamin Bouvier }; 2248a9b1a90SBenjamin Bouvier let result = bincode::serialize(&cached); 2258a9b1a90SBenjamin Bouvier (cached.stencil, result) 2268a9b1a90SBenjamin Bouvier } 2278a9b1a90SBenjamin Bouvier 2288a9b1a90SBenjamin Bouvier /// An error returned when recompiling failed. 2298a9b1a90SBenjamin Bouvier #[derive(Debug)] 2308a9b1a90SBenjamin Bouvier pub enum RecompileError { 2318a9b1a90SBenjamin Bouvier /// The version embedded in the cache entry isn't the same as cranelift's current version. 2328a9b1a90SBenjamin Bouvier VersionMismatch, 2338a9b1a90SBenjamin Bouvier /// An error occurred while deserializing the cache entry. 2348a9b1a90SBenjamin Bouvier Deserialize(bincode::Error), 2358a9b1a90SBenjamin Bouvier } 2368a9b1a90SBenjamin Bouvier 2378a9b1a90SBenjamin Bouvier impl fmt::Display for RecompileError { 2388a9b1a90SBenjamin Bouvier fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 2398a9b1a90SBenjamin Bouvier match self { 2408a9b1a90SBenjamin Bouvier RecompileError::VersionMismatch => write!(f, "cranelift version mismatch",), 2418a9b1a90SBenjamin Bouvier RecompileError::Deserialize(err) => { 2428a9b1a90SBenjamin Bouvier write!(f, "bincode failed during deserialization: {err}") 2438a9b1a90SBenjamin Bouvier } 2448a9b1a90SBenjamin Bouvier } 2458a9b1a90SBenjamin Bouvier } 2468a9b1a90SBenjamin Bouvier } 2478a9b1a90SBenjamin Bouvier 2488a9b1a90SBenjamin Bouvier /// Given a function that's been precompiled and its entry in the caching storage, try to shortcut 2498a9b1a90SBenjamin Bouvier /// compilation of the given function. 2508a9b1a90SBenjamin Bouvier /// 2518a9b1a90SBenjamin Bouvier /// Precondition: the bytes must have retrieved from a cache store entry which hash value 2528a9b1a90SBenjamin Bouvier /// is strictly the same as the `Function`'s computed hash retrieved from `compute_cache_key`. 2538a9b1a90SBenjamin Bouvier pub fn try_finish_recompile(func: &Function, bytes: &[u8]) -> Result<CompiledCode, RecompileError> { 2548a9b1a90SBenjamin Bouvier match bincode::deserialize::<CachedFunc>(bytes) { 2558a9b1a90SBenjamin Bouvier Ok(result) => { 2568a9b1a90SBenjamin Bouvier if result.version_marker != func.stencil.version_marker { 2578a9b1a90SBenjamin Bouvier Err(RecompileError::VersionMismatch) 2588a9b1a90SBenjamin Bouvier } else { 2598a9b1a90SBenjamin Bouvier Ok(result.stencil.apply_params(&func.params)) 2608a9b1a90SBenjamin Bouvier } 2618a9b1a90SBenjamin Bouvier } 2628a9b1a90SBenjamin Bouvier Err(err) => Err(RecompileError::Deserialize(err)), 2638a9b1a90SBenjamin Bouvier } 2648a9b1a90SBenjamin Bouvier } 265