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