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;
2890ac295eSAlex Crichton use crate::ir::function::{FunctionStencil, VersionMarker};
298a9b1a90SBenjamin Bouvier use crate::machinst::{CompiledCode, CompiledCodeStencil};
308a9b1a90SBenjamin Bouvier use crate::result::CompileResult;
3190ac295eSAlex Crichton use crate::{CompileError, Context, trace};
328a9b1a90SBenjamin Bouvier use crate::{isa::TargetIsa, timing};
338a9b1a90SBenjamin Bouvier use alloc::borrow::{Cow, ToOwned as _};
348a9b1a90SBenjamin Bouvier use alloc::string::ToString as _;
357eb89140SRemo Senekowitsch use cranelift_control::ControlPlane;
368a9b1a90SBenjamin Bouvier 
378a9b1a90SBenjamin Bouvier impl Context {
388a9b1a90SBenjamin Bouvier     /// Compile the function, as in `compile`, but tries to reuse compiled artifacts from former
398a9b1a90SBenjamin Bouvier     /// compilations using the provided cache store.
408a9b1a90SBenjamin Bouvier     pub fn compile_with_cache(
418a9b1a90SBenjamin Bouvier         &mut self,
428a9b1a90SBenjamin Bouvier         isa: &dyn TargetIsa,
438a9b1a90SBenjamin Bouvier         cache_store: &mut dyn CacheKvStore,
447eb89140SRemo Senekowitsch         ctrl_plane: &mut ControlPlane,
458a42768fSAlex Crichton     ) -> CompileResult<'_, (&CompiledCode, bool)> {
468a9b1a90SBenjamin Bouvier         let cache_key_hash = {
478a9b1a90SBenjamin Bouvier             let _tt = timing::try_incremental_cache();
488a9b1a90SBenjamin Bouvier 
4949bab6dbSbjorn3             let cache_key_hash = compute_cache_key(isa, &self.func);
508a9b1a90SBenjamin Bouvier 
518a9b1a90SBenjamin Bouvier             if let Some(blob) = cache_store.get(&cache_key_hash.0) {
528a9b1a90SBenjamin Bouvier                 match try_finish_recompile(&self.func, &blob) {
538a9b1a90SBenjamin Bouvier                     Ok(compiled_code) => {
548a9b1a90SBenjamin Bouvier                         let info = compiled_code.code_info();
558a9b1a90SBenjamin Bouvier 
568a9b1a90SBenjamin Bouvier                         if isa.flags().enable_incremental_compilation_cache_checks() {
577eb89140SRemo Senekowitsch                             let actual_result = self.compile(isa, ctrl_plane)?;
588a9b1a90SBenjamin Bouvier                             assert_eq!(*actual_result, compiled_code);
598a9b1a90SBenjamin Bouvier                             assert_eq!(actual_result.code_info(), info);
608a9b1a90SBenjamin Bouvier                             // no need to set `compiled_code` here, it's set by `compile()`.
618a9b1a90SBenjamin Bouvier                             return Ok((actual_result, true));
628a9b1a90SBenjamin Bouvier                         }
638a9b1a90SBenjamin Bouvier 
648a9b1a90SBenjamin Bouvier                         let compiled_code = self.compiled_code.insert(compiled_code);
658a9b1a90SBenjamin Bouvier                         return Ok((compiled_code, true));
668a9b1a90SBenjamin Bouvier                     }
678a9b1a90SBenjamin Bouvier                     Err(err) => {
688a9b1a90SBenjamin Bouvier                         trace!("error when finishing recompilation: {err}");
698a9b1a90SBenjamin Bouvier                     }
708a9b1a90SBenjamin Bouvier                 }
718a9b1a90SBenjamin Bouvier             }
728a9b1a90SBenjamin Bouvier 
738a9b1a90SBenjamin Bouvier             cache_key_hash
748a9b1a90SBenjamin Bouvier         };
758a9b1a90SBenjamin Bouvier 
767eb89140SRemo Senekowitsch         let stencil = self
777eb89140SRemo Senekowitsch             .compile_stencil(isa, ctrl_plane)
787eb89140SRemo Senekowitsch             .map_err(|err| CompileError {
798a9b1a90SBenjamin Bouvier                 inner: err,
808a9b1a90SBenjamin Bouvier                 func: &self.func,
818a9b1a90SBenjamin Bouvier             })?;
828a9b1a90SBenjamin Bouvier 
838a9b1a90SBenjamin Bouvier         let stencil = {
848a9b1a90SBenjamin Bouvier             let _tt = timing::store_incremental_cache();
858a9b1a90SBenjamin Bouvier             let (stencil, res) = serialize_compiled(stencil);
868a9b1a90SBenjamin Bouvier             if let Ok(blob) = res {
878a9b1a90SBenjamin Bouvier                 cache_store.insert(&cache_key_hash.0, blob);
888a9b1a90SBenjamin Bouvier             }
898a9b1a90SBenjamin Bouvier             stencil
908a9b1a90SBenjamin Bouvier         };
918a9b1a90SBenjamin Bouvier 
928a9b1a90SBenjamin Bouvier         let compiled_code = self
938a9b1a90SBenjamin Bouvier             .compiled_code
948a9b1a90SBenjamin Bouvier             .insert(stencil.apply_params(&self.func.params));
958a9b1a90SBenjamin Bouvier 
968a9b1a90SBenjamin Bouvier         Ok((compiled_code, false))
978a9b1a90SBenjamin Bouvier     }
988a9b1a90SBenjamin Bouvier }
998a9b1a90SBenjamin Bouvier 
1008a9b1a90SBenjamin Bouvier /// Backing storage for an incremental compilation cache, when enabled.
1018a9b1a90SBenjamin Bouvier pub trait CacheKvStore {
1028a9b1a90SBenjamin Bouvier     /// Given a cache key hash, retrieves the associated opaque serialized data.
103*5fe1c8e2SAlex Crichton     fn get(&self, key: &[u8]) -> Option<Cow<'_, [u8]>>;
1048a9b1a90SBenjamin Bouvier 
1058a9b1a90SBenjamin Bouvier     /// Given a new cache key and a serialized blob obtained from `serialize_compiled`, stores it
1068a9b1a90SBenjamin Bouvier     /// in the cache store.
1078a9b1a90SBenjamin Bouvier     fn insert(&mut self, key: &[u8], val: Vec<u8>);
1088a9b1a90SBenjamin Bouvier }
1098a9b1a90SBenjamin Bouvier 
1108a9b1a90SBenjamin Bouvier /// Hashed `CachedKey`, to use as an identifier when looking up whether a function has already been
1118a9b1a90SBenjamin Bouvier /// compiled or not.
1128a9b1a90SBenjamin Bouvier #[derive(Clone, Hash, PartialEq, Eq)]
1138a9b1a90SBenjamin Bouvier pub struct CacheKeyHash([u8; 32]);
1148a9b1a90SBenjamin Bouvier 
1158a9b1a90SBenjamin Bouvier impl std::fmt::Display for CacheKeyHash {
1168a9b1a90SBenjamin Bouvier     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1178a9b1a90SBenjamin Bouvier         write!(f, "CacheKeyHash:{:?}", self.0)
1188a9b1a90SBenjamin Bouvier     }
1198a9b1a90SBenjamin Bouvier }
1208a9b1a90SBenjamin Bouvier 
1219ec02f9dSChristopher Serr #[derive(serde_derive::Serialize, serde_derive::Deserialize)]
1228a9b1a90SBenjamin Bouvier struct CachedFunc {
123fc3c5d24Sbjorn3     // Note: The version marker must be first to ensure deserialization stops in case of a version
124fc3c5d24Sbjorn3     // mismatch before attempting to deserialize the actual compiled code.
1258a9b1a90SBenjamin Bouvier     version_marker: VersionMarker,
126fc3c5d24Sbjorn3     stencil: CompiledCodeStencil,
1278a9b1a90SBenjamin Bouvier }
1288a9b1a90SBenjamin Bouvier 
1298a9b1a90SBenjamin Bouvier /// Key for caching a single function's compilation.
1308a9b1a90SBenjamin Bouvier ///
1318a9b1a90SBenjamin Bouvier /// If two functions get the same `CacheKey`, then we can reuse the compiled artifacts, modulo some
1328a9b1a90SBenjamin Bouvier /// fixups.
1338a9b1a90SBenjamin Bouvier ///
1348a9b1a90SBenjamin Bouvier /// Note: the key will be invalidated across different versions of cranelift, as the
1358a9b1a90SBenjamin Bouvier /// `FunctionStencil` contains a `VersionMarker` itself.
1368a9b1a90SBenjamin Bouvier #[derive(Hash)]
1378a9b1a90SBenjamin Bouvier struct CacheKey<'a> {
1388a9b1a90SBenjamin Bouvier     stencil: &'a FunctionStencil,
1398a9b1a90SBenjamin Bouvier     parameters: CompileParameters,
1408a9b1a90SBenjamin Bouvier }
1418a9b1a90SBenjamin Bouvier 
1429ec02f9dSChristopher Serr #[derive(Clone, PartialEq, Hash, serde_derive::Serialize, serde_derive::Deserialize)]
1438a9b1a90SBenjamin Bouvier struct CompileParameters {
1448a9b1a90SBenjamin Bouvier     isa: String,
1458a9b1a90SBenjamin Bouvier     triple: String,
1468a9b1a90SBenjamin Bouvier     flags: String,
1478a9b1a90SBenjamin Bouvier     isa_flags: Vec<String>,
1488a9b1a90SBenjamin Bouvier }
1498a9b1a90SBenjamin Bouvier 
1508a9b1a90SBenjamin Bouvier impl CompileParameters {
1518a9b1a90SBenjamin Bouvier     fn from_isa(isa: &dyn TargetIsa) -> Self {
1528a9b1a90SBenjamin Bouvier         Self {
1538a9b1a90SBenjamin Bouvier             isa: isa.name().to_owned(),
1548a9b1a90SBenjamin Bouvier             triple: isa.triple().to_string(),
1558a9b1a90SBenjamin Bouvier             flags: isa.flags().to_string(),
1568a9b1a90SBenjamin Bouvier             isa_flags: isa
1578a9b1a90SBenjamin Bouvier                 .isa_flags()
1588a9b1a90SBenjamin Bouvier                 .into_iter()
1598a9b1a90SBenjamin Bouvier                 .map(|v| v.value_string())
1608a9b1a90SBenjamin Bouvier                 .collect(),
1618a9b1a90SBenjamin Bouvier         }
1628a9b1a90SBenjamin Bouvier     }
1638a9b1a90SBenjamin Bouvier }
1648a9b1a90SBenjamin Bouvier 
1658a9b1a90SBenjamin Bouvier impl<'a> CacheKey<'a> {
1668a9b1a90SBenjamin Bouvier     /// Creates a new cache store key for a function.
1678a9b1a90SBenjamin Bouvier     ///
1688a9b1a90SBenjamin Bouvier     /// This is a bit expensive to compute, so it should be cached and reused as much as possible.
16949bab6dbSbjorn3     fn new(isa: &dyn TargetIsa, f: &'a Function) -> Self {
1708a9b1a90SBenjamin Bouvier         CacheKey {
1718a9b1a90SBenjamin Bouvier             stencil: &f.stencil,
1728a9b1a90SBenjamin Bouvier             parameters: CompileParameters::from_isa(isa),
1738a9b1a90SBenjamin Bouvier         }
1748a9b1a90SBenjamin Bouvier     }
1758a9b1a90SBenjamin Bouvier }
1768a9b1a90SBenjamin Bouvier 
1778a9b1a90SBenjamin Bouvier /// Compute a cache key, and hash it on your behalf.
1788a9b1a90SBenjamin Bouvier ///
1798a9b1a90SBenjamin Bouvier /// Since computing the `CacheKey` is a bit expensive, it should be done as least as possible.
18049bab6dbSbjorn3 pub fn compute_cache_key(isa: &dyn TargetIsa, func: &Function) -> CacheKeyHash {
1818a9b1a90SBenjamin Bouvier     use core::hash::{Hash as _, Hasher};
1828a9b1a90SBenjamin Bouvier     use sha2::Digest as _;
1838a9b1a90SBenjamin Bouvier 
1848a9b1a90SBenjamin Bouvier     struct Sha256Hasher(sha2::Sha256);
1858a9b1a90SBenjamin Bouvier 
1868a9b1a90SBenjamin Bouvier     impl Hasher for Sha256Hasher {
1878a9b1a90SBenjamin Bouvier         fn finish(&self) -> u64 {
1888a9b1a90SBenjamin Bouvier             panic!("Sha256Hasher doesn't support finish!");
1898a9b1a90SBenjamin Bouvier         }
1908a9b1a90SBenjamin Bouvier         fn write(&mut self, bytes: &[u8]) {
1918a9b1a90SBenjamin Bouvier             self.0.update(bytes);
1928a9b1a90SBenjamin Bouvier         }
1938a9b1a90SBenjamin Bouvier     }
1948a9b1a90SBenjamin Bouvier 
1958a9b1a90SBenjamin Bouvier     let cache_key = CacheKey::new(isa, func);
1968a9b1a90SBenjamin Bouvier 
1978a9b1a90SBenjamin Bouvier     let mut hasher = Sha256Hasher(sha2::Sha256::new());
1988a9b1a90SBenjamin Bouvier     cache_key.hash(&mut hasher);
1998a9b1a90SBenjamin Bouvier     let hash: [u8; 32] = hasher.0.finalize().into();
2008a9b1a90SBenjamin Bouvier 
2018a9b1a90SBenjamin Bouvier     CacheKeyHash(hash)
2028a9b1a90SBenjamin Bouvier }
2038a9b1a90SBenjamin Bouvier 
2048a9b1a90SBenjamin Bouvier /// Given a function that's been successfully compiled, serialize it to a blob that the caller may
2058a9b1a90SBenjamin Bouvier /// store somewhere for future use by `try_finish_recompile`.
2068a9b1a90SBenjamin Bouvier ///
2078a9b1a90SBenjamin Bouvier /// As this function requires ownership on the `CompiledCodeStencil`, it gives it back at the end
2088a9b1a90SBenjamin Bouvier /// of the function call. The value is left untouched.
2098a9b1a90SBenjamin Bouvier pub fn serialize_compiled(
2108a9b1a90SBenjamin Bouvier     result: CompiledCodeStencil,
21160539520SAlex Crichton ) -> (CompiledCodeStencil, Result<Vec<u8>, postcard::Error>) {
2128a9b1a90SBenjamin Bouvier     let cached = CachedFunc {
2138a9b1a90SBenjamin Bouvier         version_marker: VersionMarker,
214fc3c5d24Sbjorn3         stencil: result,
2158a9b1a90SBenjamin Bouvier     };
21660539520SAlex Crichton     let result = postcard::to_allocvec(&cached);
2178a9b1a90SBenjamin Bouvier     (cached.stencil, result)
2188a9b1a90SBenjamin Bouvier }
2198a9b1a90SBenjamin Bouvier 
2208a9b1a90SBenjamin Bouvier /// An error returned when recompiling failed.
2218a9b1a90SBenjamin Bouvier #[derive(Debug)]
2228a9b1a90SBenjamin Bouvier pub enum RecompileError {
2238a9b1a90SBenjamin Bouvier     /// The version embedded in the cache entry isn't the same as cranelift's current version.
2248a9b1a90SBenjamin Bouvier     VersionMismatch,
2258a9b1a90SBenjamin Bouvier     /// An error occurred while deserializing the cache entry.
22660539520SAlex Crichton     Deserialize(postcard::Error),
2278a9b1a90SBenjamin Bouvier }
2288a9b1a90SBenjamin Bouvier 
2298a9b1a90SBenjamin Bouvier impl fmt::Display for RecompileError {
2308a9b1a90SBenjamin Bouvier     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2318a9b1a90SBenjamin Bouvier         match self {
2328a9b1a90SBenjamin Bouvier             RecompileError::VersionMismatch => write!(f, "cranelift version mismatch",),
2338a9b1a90SBenjamin Bouvier             RecompileError::Deserialize(err) => {
23460539520SAlex Crichton                 write!(f, "postcard failed during deserialization: {err}")
2358a9b1a90SBenjamin Bouvier             }
2368a9b1a90SBenjamin Bouvier         }
2378a9b1a90SBenjamin Bouvier     }
2388a9b1a90SBenjamin Bouvier }
2398a9b1a90SBenjamin Bouvier 
2408a9b1a90SBenjamin Bouvier /// Given a function that's been precompiled and its entry in the caching storage, try to shortcut
2418a9b1a90SBenjamin Bouvier /// compilation of the given function.
2428a9b1a90SBenjamin Bouvier ///
2438a9b1a90SBenjamin Bouvier /// Precondition: the bytes must have retrieved from a cache store entry which hash value
2448a9b1a90SBenjamin Bouvier /// is strictly the same as the `Function`'s computed hash retrieved from `compute_cache_key`.
2458a9b1a90SBenjamin Bouvier pub fn try_finish_recompile(func: &Function, bytes: &[u8]) -> Result<CompiledCode, RecompileError> {
24660539520SAlex Crichton     match postcard::from_bytes::<CachedFunc>(bytes) {
2478a9b1a90SBenjamin Bouvier         Ok(result) => {
2488a9b1a90SBenjamin Bouvier             if result.version_marker != func.stencil.version_marker {
2498a9b1a90SBenjamin Bouvier                 Err(RecompileError::VersionMismatch)
2508a9b1a90SBenjamin Bouvier             } else {
2518a9b1a90SBenjamin Bouvier                 Ok(result.stencil.apply_params(&func.params))
2528a9b1a90SBenjamin Bouvier             }
2538a9b1a90SBenjamin Bouvier         }
2548a9b1a90SBenjamin Bouvier         Err(err) => Err(RecompileError::Deserialize(err)),
2558a9b1a90SBenjamin Bouvier     }
2568a9b1a90SBenjamin Bouvier }
257