1*8a9b1a90SBenjamin Bouvier //! This module provides a set of primitives that allow implementing an incremental cache on top of 2*8a9b1a90SBenjamin Bouvier //! Cranelift, making it possible to reuse previous compiled artifacts for functions that have been 3*8a9b1a90SBenjamin Bouvier //! compiled previously. 4*8a9b1a90SBenjamin Bouvier //! 5*8a9b1a90SBenjamin Bouvier //! This set of operation is experimental and can be enabled using the Cargo feature 6*8a9b1a90SBenjamin Bouvier //! `incremental-cache`. 7*8a9b1a90SBenjamin Bouvier //! 8*8a9b1a90SBenjamin Bouvier //! This can bring speedups in different cases: change-code-and-immediately-recompile iterations 9*8a9b1a90SBenjamin Bouvier //! get faster, modules sharing lots of code can reuse each other's artifacts, etc. 10*8a9b1a90SBenjamin Bouvier //! 11*8a9b1a90SBenjamin Bouvier //! The three main primitives are the following: 12*8a9b1a90SBenjamin Bouvier //! - `compute_cache_key` is used to compute the cache key associated to a `Function`. This is 13*8a9b1a90SBenjamin Bouvier //! basically the content of the function, modulo a few things the caching system is resilient to. 14*8a9b1a90SBenjamin Bouvier //! - `serialize_compiled` is used to serialize the result of a compilation, so it can be reused 15*8a9b1a90SBenjamin Bouvier //! later on by... 16*8a9b1a90SBenjamin Bouvier //! - `try_finish_recompile`, which reads binary blobs serialized with `serialize_compiled`, 17*8a9b1a90SBenjamin Bouvier //! re-creating the compilation artifact from those. 18*8a9b1a90SBenjamin Bouvier //! 19*8a9b1a90SBenjamin Bouvier //! The `CacheStore` trait and `Context::compile_with_cache` method are provided as 20*8a9b1a90SBenjamin Bouvier //! high-level, easy-to-use facilities to make use of that cache, and show an example of how to use 21*8a9b1a90SBenjamin Bouvier //! the above three primitives to form a full incremental caching system. 22*8a9b1a90SBenjamin Bouvier 23*8a9b1a90SBenjamin Bouvier use core::fmt; 24*8a9b1a90SBenjamin Bouvier 25*8a9b1a90SBenjamin Bouvier use crate::alloc::string::String; 26*8a9b1a90SBenjamin Bouvier use crate::alloc::vec::Vec; 27*8a9b1a90SBenjamin Bouvier use crate::ir::function::{FunctionStencil, VersionMarker}; 28*8a9b1a90SBenjamin Bouvier use crate::ir::Function; 29*8a9b1a90SBenjamin Bouvier use crate::machinst::{CompiledCode, CompiledCodeStencil}; 30*8a9b1a90SBenjamin Bouvier use crate::result::CompileResult; 31*8a9b1a90SBenjamin Bouvier use crate::{isa::TargetIsa, timing}; 32*8a9b1a90SBenjamin Bouvier use crate::{trace, CompileError, Context}; 33*8a9b1a90SBenjamin Bouvier use alloc::borrow::{Cow, ToOwned as _}; 34*8a9b1a90SBenjamin Bouvier use alloc::string::ToString as _; 35*8a9b1a90SBenjamin Bouvier 36*8a9b1a90SBenjamin Bouvier impl Context { 37*8a9b1a90SBenjamin Bouvier /// Compile the function, as in `compile`, but tries to reuse compiled artifacts from former 38*8a9b1a90SBenjamin Bouvier /// compilations using the provided cache store. 39*8a9b1a90SBenjamin Bouvier pub fn compile_with_cache( 40*8a9b1a90SBenjamin Bouvier &mut self, 41*8a9b1a90SBenjamin Bouvier isa: &dyn TargetIsa, 42*8a9b1a90SBenjamin Bouvier cache_store: &mut dyn CacheKvStore, 43*8a9b1a90SBenjamin Bouvier ) -> CompileResult<(&CompiledCode, bool)> { 44*8a9b1a90SBenjamin Bouvier let cache_key_hash = { 45*8a9b1a90SBenjamin Bouvier let _tt = timing::try_incremental_cache(); 46*8a9b1a90SBenjamin Bouvier 47*8a9b1a90SBenjamin Bouvier let cache_key_hash = compute_cache_key(isa, &mut self.func); 48*8a9b1a90SBenjamin Bouvier 49*8a9b1a90SBenjamin Bouvier if let Some(blob) = cache_store.get(&cache_key_hash.0) { 50*8a9b1a90SBenjamin Bouvier match try_finish_recompile(&self.func, &blob) { 51*8a9b1a90SBenjamin Bouvier Ok(compiled_code) => { 52*8a9b1a90SBenjamin Bouvier let info = compiled_code.code_info(); 53*8a9b1a90SBenjamin Bouvier 54*8a9b1a90SBenjamin Bouvier if isa.flags().enable_incremental_compilation_cache_checks() { 55*8a9b1a90SBenjamin Bouvier let actual_result = self.compile(isa)?; 56*8a9b1a90SBenjamin Bouvier assert_eq!(*actual_result, compiled_code); 57*8a9b1a90SBenjamin Bouvier assert_eq!(actual_result.code_info(), info); 58*8a9b1a90SBenjamin Bouvier // no need to set `compiled_code` here, it's set by `compile()`. 59*8a9b1a90SBenjamin Bouvier return Ok((actual_result, true)); 60*8a9b1a90SBenjamin Bouvier } 61*8a9b1a90SBenjamin Bouvier 62*8a9b1a90SBenjamin Bouvier let compiled_code = self.compiled_code.insert(compiled_code); 63*8a9b1a90SBenjamin Bouvier return Ok((compiled_code, true)); 64*8a9b1a90SBenjamin Bouvier } 65*8a9b1a90SBenjamin Bouvier Err(err) => { 66*8a9b1a90SBenjamin Bouvier trace!("error when finishing recompilation: {err}"); 67*8a9b1a90SBenjamin Bouvier } 68*8a9b1a90SBenjamin Bouvier } 69*8a9b1a90SBenjamin Bouvier } 70*8a9b1a90SBenjamin Bouvier 71*8a9b1a90SBenjamin Bouvier cache_key_hash 72*8a9b1a90SBenjamin Bouvier }; 73*8a9b1a90SBenjamin Bouvier 74*8a9b1a90SBenjamin Bouvier let stencil = self.compile_stencil(isa).map_err(|err| CompileError { 75*8a9b1a90SBenjamin Bouvier inner: err, 76*8a9b1a90SBenjamin Bouvier func: &self.func, 77*8a9b1a90SBenjamin Bouvier })?; 78*8a9b1a90SBenjamin Bouvier 79*8a9b1a90SBenjamin Bouvier let stencil = { 80*8a9b1a90SBenjamin Bouvier let _tt = timing::store_incremental_cache(); 81*8a9b1a90SBenjamin Bouvier let (stencil, res) = serialize_compiled(stencil); 82*8a9b1a90SBenjamin Bouvier if let Ok(blob) = res { 83*8a9b1a90SBenjamin Bouvier cache_store.insert(&cache_key_hash.0, blob); 84*8a9b1a90SBenjamin Bouvier } 85*8a9b1a90SBenjamin Bouvier stencil 86*8a9b1a90SBenjamin Bouvier }; 87*8a9b1a90SBenjamin Bouvier 88*8a9b1a90SBenjamin Bouvier let compiled_code = self 89*8a9b1a90SBenjamin Bouvier .compiled_code 90*8a9b1a90SBenjamin Bouvier .insert(stencil.apply_params(&self.func.params)); 91*8a9b1a90SBenjamin Bouvier 92*8a9b1a90SBenjamin Bouvier Ok((compiled_code, false)) 93*8a9b1a90SBenjamin Bouvier } 94*8a9b1a90SBenjamin Bouvier } 95*8a9b1a90SBenjamin Bouvier 96*8a9b1a90SBenjamin Bouvier /// Backing storage for an incremental compilation cache, when enabled. 97*8a9b1a90SBenjamin Bouvier pub trait CacheKvStore { 98*8a9b1a90SBenjamin Bouvier /// Given a cache key hash, retrieves the associated opaque serialized data. 99*8a9b1a90SBenjamin Bouvier fn get(&self, key: &[u8]) -> Option<Cow<[u8]>>; 100*8a9b1a90SBenjamin Bouvier 101*8a9b1a90SBenjamin Bouvier /// Given a new cache key and a serialized blob obtained from `serialize_compiled`, stores it 102*8a9b1a90SBenjamin Bouvier /// in the cache store. 103*8a9b1a90SBenjamin Bouvier fn insert(&mut self, key: &[u8], val: Vec<u8>); 104*8a9b1a90SBenjamin Bouvier } 105*8a9b1a90SBenjamin Bouvier 106*8a9b1a90SBenjamin Bouvier /// Hashed `CachedKey`, to use as an identifier when looking up whether a function has already been 107*8a9b1a90SBenjamin Bouvier /// compiled or not. 108*8a9b1a90SBenjamin Bouvier #[derive(Clone, Hash, PartialEq, Eq)] 109*8a9b1a90SBenjamin Bouvier pub struct CacheKeyHash([u8; 32]); 110*8a9b1a90SBenjamin Bouvier 111*8a9b1a90SBenjamin Bouvier impl std::fmt::Display for CacheKeyHash { 112*8a9b1a90SBenjamin Bouvier fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 113*8a9b1a90SBenjamin Bouvier write!(f, "CacheKeyHash:{:?}", self.0) 114*8a9b1a90SBenjamin Bouvier } 115*8a9b1a90SBenjamin Bouvier } 116*8a9b1a90SBenjamin Bouvier 117*8a9b1a90SBenjamin Bouvier #[derive(serde::Serialize, serde::Deserialize)] 118*8a9b1a90SBenjamin Bouvier struct CachedFunc { 119*8a9b1a90SBenjamin Bouvier stencil: CompiledCodeStencil, 120*8a9b1a90SBenjamin Bouvier version_marker: VersionMarker, 121*8a9b1a90SBenjamin Bouvier } 122*8a9b1a90SBenjamin Bouvier 123*8a9b1a90SBenjamin Bouvier /// Key for caching a single function's compilation. 124*8a9b1a90SBenjamin Bouvier /// 125*8a9b1a90SBenjamin Bouvier /// If two functions get the same `CacheKey`, then we can reuse the compiled artifacts, modulo some 126*8a9b1a90SBenjamin Bouvier /// fixups. 127*8a9b1a90SBenjamin Bouvier /// 128*8a9b1a90SBenjamin Bouvier /// Note: the key will be invalidated across different versions of cranelift, as the 129*8a9b1a90SBenjamin Bouvier /// `FunctionStencil` contains a `VersionMarker` itself. 130*8a9b1a90SBenjamin Bouvier #[derive(Hash)] 131*8a9b1a90SBenjamin Bouvier struct CacheKey<'a> { 132*8a9b1a90SBenjamin Bouvier stencil: &'a FunctionStencil, 133*8a9b1a90SBenjamin Bouvier parameters: CompileParameters, 134*8a9b1a90SBenjamin Bouvier } 135*8a9b1a90SBenjamin Bouvier 136*8a9b1a90SBenjamin Bouvier #[derive(Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)] 137*8a9b1a90SBenjamin Bouvier struct CompileParameters { 138*8a9b1a90SBenjamin Bouvier isa: String, 139*8a9b1a90SBenjamin Bouvier triple: String, 140*8a9b1a90SBenjamin Bouvier flags: String, 141*8a9b1a90SBenjamin Bouvier isa_flags: Vec<String>, 142*8a9b1a90SBenjamin Bouvier } 143*8a9b1a90SBenjamin Bouvier 144*8a9b1a90SBenjamin Bouvier impl CompileParameters { 145*8a9b1a90SBenjamin Bouvier fn from_isa(isa: &dyn TargetIsa) -> Self { 146*8a9b1a90SBenjamin Bouvier Self { 147*8a9b1a90SBenjamin Bouvier isa: isa.name().to_owned(), 148*8a9b1a90SBenjamin Bouvier triple: isa.triple().to_string(), 149*8a9b1a90SBenjamin Bouvier flags: isa.flags().to_string(), 150*8a9b1a90SBenjamin Bouvier isa_flags: isa 151*8a9b1a90SBenjamin Bouvier .isa_flags() 152*8a9b1a90SBenjamin Bouvier .into_iter() 153*8a9b1a90SBenjamin Bouvier .map(|v| v.value_string()) 154*8a9b1a90SBenjamin Bouvier .collect(), 155*8a9b1a90SBenjamin Bouvier } 156*8a9b1a90SBenjamin Bouvier } 157*8a9b1a90SBenjamin Bouvier } 158*8a9b1a90SBenjamin Bouvier 159*8a9b1a90SBenjamin Bouvier impl<'a> CacheKey<'a> { 160*8a9b1a90SBenjamin Bouvier /// Creates a new cache store key for a function. 161*8a9b1a90SBenjamin Bouvier /// 162*8a9b1a90SBenjamin Bouvier /// This is a bit expensive to compute, so it should be cached and reused as much as possible. 163*8a9b1a90SBenjamin Bouvier fn new(isa: &dyn TargetIsa, f: &'a mut Function) -> Self { 164*8a9b1a90SBenjamin Bouvier // Make sure the blocks and instructions are sequenced the same way as we might 165*8a9b1a90SBenjamin Bouvier // have serialized them earlier. This is the symmetric of what's done in 166*8a9b1a90SBenjamin Bouvier // `try_load`. 167*8a9b1a90SBenjamin Bouvier f.stencil.layout.full_renumber(); 168*8a9b1a90SBenjamin Bouvier CacheKey { 169*8a9b1a90SBenjamin Bouvier stencil: &f.stencil, 170*8a9b1a90SBenjamin Bouvier parameters: CompileParameters::from_isa(isa), 171*8a9b1a90SBenjamin Bouvier } 172*8a9b1a90SBenjamin Bouvier } 173*8a9b1a90SBenjamin Bouvier } 174*8a9b1a90SBenjamin Bouvier 175*8a9b1a90SBenjamin Bouvier /// Compute a cache key, and hash it on your behalf. 176*8a9b1a90SBenjamin Bouvier /// 177*8a9b1a90SBenjamin Bouvier /// Since computing the `CacheKey` is a bit expensive, it should be done as least as possible. 178*8a9b1a90SBenjamin Bouvier pub fn compute_cache_key(isa: &dyn TargetIsa, func: &mut Function) -> CacheKeyHash { 179*8a9b1a90SBenjamin Bouvier use core::hash::{Hash as _, Hasher}; 180*8a9b1a90SBenjamin Bouvier use sha2::Digest as _; 181*8a9b1a90SBenjamin Bouvier 182*8a9b1a90SBenjamin Bouvier struct Sha256Hasher(sha2::Sha256); 183*8a9b1a90SBenjamin Bouvier 184*8a9b1a90SBenjamin Bouvier impl Hasher for Sha256Hasher { 185*8a9b1a90SBenjamin Bouvier fn finish(&self) -> u64 { 186*8a9b1a90SBenjamin Bouvier panic!("Sha256Hasher doesn't support finish!"); 187*8a9b1a90SBenjamin Bouvier } 188*8a9b1a90SBenjamin Bouvier fn write(&mut self, bytes: &[u8]) { 189*8a9b1a90SBenjamin Bouvier self.0.update(bytes); 190*8a9b1a90SBenjamin Bouvier } 191*8a9b1a90SBenjamin Bouvier } 192*8a9b1a90SBenjamin Bouvier 193*8a9b1a90SBenjamin Bouvier let cache_key = CacheKey::new(isa, func); 194*8a9b1a90SBenjamin Bouvier 195*8a9b1a90SBenjamin Bouvier let mut hasher = Sha256Hasher(sha2::Sha256::new()); 196*8a9b1a90SBenjamin Bouvier cache_key.hash(&mut hasher); 197*8a9b1a90SBenjamin Bouvier let hash: [u8; 32] = hasher.0.finalize().into(); 198*8a9b1a90SBenjamin Bouvier 199*8a9b1a90SBenjamin Bouvier CacheKeyHash(hash) 200*8a9b1a90SBenjamin Bouvier } 201*8a9b1a90SBenjamin Bouvier 202*8a9b1a90SBenjamin Bouvier /// Given a function that's been successfully compiled, serialize it to a blob that the caller may 203*8a9b1a90SBenjamin Bouvier /// store somewhere for future use by `try_finish_recompile`. 204*8a9b1a90SBenjamin Bouvier /// 205*8a9b1a90SBenjamin Bouvier /// As this function requires ownership on the `CompiledCodeStencil`, it gives it back at the end 206*8a9b1a90SBenjamin Bouvier /// of the function call. The value is left untouched. 207*8a9b1a90SBenjamin Bouvier pub fn serialize_compiled( 208*8a9b1a90SBenjamin Bouvier result: CompiledCodeStencil, 209*8a9b1a90SBenjamin Bouvier ) -> (CompiledCodeStencil, Result<Vec<u8>, bincode::Error>) { 210*8a9b1a90SBenjamin Bouvier let cached = CachedFunc { 211*8a9b1a90SBenjamin Bouvier stencil: result, 212*8a9b1a90SBenjamin Bouvier version_marker: VersionMarker, 213*8a9b1a90SBenjamin Bouvier }; 214*8a9b1a90SBenjamin Bouvier let result = bincode::serialize(&cached); 215*8a9b1a90SBenjamin Bouvier (cached.stencil, result) 216*8a9b1a90SBenjamin Bouvier } 217*8a9b1a90SBenjamin Bouvier 218*8a9b1a90SBenjamin Bouvier /// An error returned when recompiling failed. 219*8a9b1a90SBenjamin Bouvier #[derive(Debug)] 220*8a9b1a90SBenjamin Bouvier pub enum RecompileError { 221*8a9b1a90SBenjamin Bouvier /// The version embedded in the cache entry isn't the same as cranelift's current version. 222*8a9b1a90SBenjamin Bouvier VersionMismatch, 223*8a9b1a90SBenjamin Bouvier /// An error occurred while deserializing the cache entry. 224*8a9b1a90SBenjamin Bouvier Deserialize(bincode::Error), 225*8a9b1a90SBenjamin Bouvier } 226*8a9b1a90SBenjamin Bouvier 227*8a9b1a90SBenjamin Bouvier impl fmt::Display for RecompileError { 228*8a9b1a90SBenjamin Bouvier fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 229*8a9b1a90SBenjamin Bouvier match self { 230*8a9b1a90SBenjamin Bouvier RecompileError::VersionMismatch => write!(f, "cranelift version mismatch",), 231*8a9b1a90SBenjamin Bouvier RecompileError::Deserialize(err) => { 232*8a9b1a90SBenjamin Bouvier write!(f, "bincode failed during deserialization: {err}") 233*8a9b1a90SBenjamin Bouvier } 234*8a9b1a90SBenjamin Bouvier } 235*8a9b1a90SBenjamin Bouvier } 236*8a9b1a90SBenjamin Bouvier } 237*8a9b1a90SBenjamin Bouvier 238*8a9b1a90SBenjamin Bouvier /// Given a function that's been precompiled and its entry in the caching storage, try to shortcut 239*8a9b1a90SBenjamin Bouvier /// compilation of the given function. 240*8a9b1a90SBenjamin Bouvier /// 241*8a9b1a90SBenjamin Bouvier /// Precondition: the bytes must have retrieved from a cache store entry which hash value 242*8a9b1a90SBenjamin Bouvier /// is strictly the same as the `Function`'s computed hash retrieved from `compute_cache_key`. 243*8a9b1a90SBenjamin Bouvier pub fn try_finish_recompile(func: &Function, bytes: &[u8]) -> Result<CompiledCode, RecompileError> { 244*8a9b1a90SBenjamin Bouvier match bincode::deserialize::<CachedFunc>(bytes) { 245*8a9b1a90SBenjamin Bouvier Ok(result) => { 246*8a9b1a90SBenjamin Bouvier if result.version_marker != func.stencil.version_marker { 247*8a9b1a90SBenjamin Bouvier Err(RecompileError::VersionMismatch) 248*8a9b1a90SBenjamin Bouvier } else { 249*8a9b1a90SBenjamin Bouvier Ok(result.stencil.apply_params(&func.params)) 250*8a9b1a90SBenjamin Bouvier } 251*8a9b1a90SBenjamin Bouvier } 252*8a9b1a90SBenjamin Bouvier Err(err) => Err(RecompileError::Deserialize(err)), 253*8a9b1a90SBenjamin Bouvier } 254*8a9b1a90SBenjamin Bouvier } 255