Path: blob/main/cranelift/codegen/src/incremental_cache.rs
1693 views
//! This module provides a set of primitives that allow implementing an incremental cache on top of1//! Cranelift, making it possible to reuse previous compiled artifacts for functions that have been2//! compiled previously.3//!4//! This set of operation is experimental and can be enabled using the Cargo feature5//! `incremental-cache`.6//!7//! This can bring speedups in different cases: change-code-and-immediately-recompile iterations8//! get faster, modules sharing lots of code can reuse each other's artifacts, etc.9//!10//! The three main primitives are the following:11//! - `compute_cache_key` is used to compute the cache key associated to a `Function`. This is12//! basically the content of the function, modulo a few things the caching system is resilient to.13//! - `serialize_compiled` is used to serialize the result of a compilation, so it can be reused14//! later on by...15//! - `try_finish_recompile`, which reads binary blobs serialized with `serialize_compiled`,16//! re-creating the compilation artifact from those.17//!18//! The `CacheStore` trait and `Context::compile_with_cache` method are provided as19//! high-level, easy-to-use facilities to make use of that cache, and show an example of how to use20//! the above three primitives to form a full incremental caching system.2122use core::fmt;23use core::hash::{Hash, Hasher};2425use crate::alloc::vec::Vec;26use crate::ir::Function;27use crate::ir::function::{FunctionStencil, VersionMarker};28use crate::machinst::{CompiledCode, CompiledCodeStencil};29use crate::result::CompileResult;30use crate::{CompileError, Context, trace};31use crate::{isa::TargetIsa, timing};32use alloc::borrow::Cow;33use cranelift_control::ControlPlane;3435impl Context {36/// Compile the function, as in `compile`, but tries to reuse compiled artifacts from former37/// compilations using the provided cache store.38pub fn compile_with_cache(39&mut self,40isa: &dyn TargetIsa,41cache_store: &mut dyn CacheKvStore,42ctrl_plane: &mut ControlPlane,43) -> CompileResult<'_, (&CompiledCode, bool)> {44let cache_key_hash = {45let _tt = timing::try_incremental_cache();4647let cache_key_hash = compute_cache_key(isa, &self.func);4849if let Some(blob) = cache_store.get(&cache_key_hash.0) {50match try_finish_recompile(&self.func, &blob) {51Ok(compiled_code) => {52let info = compiled_code.code_info();5354if isa.flags().enable_incremental_compilation_cache_checks() {55let actual_result = self.compile(isa, ctrl_plane)?;56assert_eq!(*actual_result, compiled_code);57assert_eq!(actual_result.code_info(), info);58// no need to set `compiled_code` here, it's set by `compile()`.59return Ok((actual_result, true));60}6162let compiled_code = self.compiled_code.insert(compiled_code);63return Ok((compiled_code, true));64}65Err(err) => {66trace!("error when finishing recompilation: {err}");67}68}69}7071cache_key_hash72};7374let stencil = self75.compile_stencil(isa, ctrl_plane)76.map_err(|err| CompileError {77inner: err,78func: &self.func,79})?;8081let stencil = {82let _tt = timing::store_incremental_cache();83let (stencil, res) = serialize_compiled(stencil);84if let Ok(blob) = res {85cache_store.insert(&cache_key_hash.0, blob);86}87stencil88};8990let compiled_code = self91.compiled_code92.insert(stencil.apply_params(&self.func.params));9394Ok((compiled_code, false))95}96}9798/// Backing storage for an incremental compilation cache, when enabled.99pub trait CacheKvStore {100/// Given a cache key hash, retrieves the associated opaque serialized data.101fn get(&self, key: &[u8]) -> Option<Cow<'_, [u8]>>;102103/// Given a new cache key and a serialized blob obtained from `serialize_compiled`, stores it104/// in the cache store.105fn insert(&mut self, key: &[u8], val: Vec<u8>);106}107108/// Hashed `CachedKey`, to use as an identifier when looking up whether a function has already been109/// compiled or not.110#[derive(Clone, Hash, PartialEq, Eq)]111pub struct CacheKeyHash([u8; 32]);112113impl std::fmt::Display for CacheKeyHash {114fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {115write!(f, "CacheKeyHash:{:?}", self.0)116}117}118119#[derive(serde_derive::Serialize, serde_derive::Deserialize)]120struct CachedFunc {121// Note: The version marker must be first to ensure deserialization stops in case of a version122// mismatch before attempting to deserialize the actual compiled code.123version_marker: VersionMarker,124stencil: CompiledCodeStencil,125}126127/// Key for caching a single function's compilation.128///129/// If two functions get the same `CacheKey`, then we can reuse the compiled artifacts, modulo some130/// fixups.131///132/// Note: the key will be invalidated across different versions of cranelift, as the133/// `FunctionStencil` contains a `VersionMarker` itself.134struct CacheKey<'a> {135stencil: &'a FunctionStencil,136isa: &'a dyn TargetIsa,137}138139impl<'a> Hash for CacheKey<'a> {140fn hash<H: Hasher>(&self, state: &mut H) {141self.stencil.hash(state);142self.isa.name().hash(state);143self.isa.triple().hash(state);144self.isa.flags().hash(state);145self.isa.isa_flags_hash_key().hash(state);146}147}148149impl<'a> CacheKey<'a> {150/// Creates a new cache store key for a function.151///152/// This is a bit expensive to compute, so it should be cached and reused as much as possible.153fn new(isa: &'a dyn TargetIsa, f: &'a Function) -> Self {154CacheKey {155stencil: &f.stencil,156isa,157}158}159}160161/// Compute a cache key, and hash it on your behalf.162///163/// Since computing the `CacheKey` is a bit expensive, it should be done as least as possible.164pub fn compute_cache_key(isa: &dyn TargetIsa, func: &Function) -> CacheKeyHash {165use core::hash::{Hash as _, Hasher};166use sha2::Digest as _;167168struct Sha256Hasher(sha2::Sha256);169170impl Hasher for Sha256Hasher {171fn finish(&self) -> u64 {172panic!("Sha256Hasher doesn't support finish!");173}174fn write(&mut self, bytes: &[u8]) {175self.0.update(bytes);176}177}178179let cache_key = CacheKey::new(isa, func);180181let mut hasher = Sha256Hasher(sha2::Sha256::new());182cache_key.hash(&mut hasher);183let hash: [u8; 32] = hasher.0.finalize().into();184185CacheKeyHash(hash)186}187188/// Given a function that's been successfully compiled, serialize it to a blob that the caller may189/// store somewhere for future use by `try_finish_recompile`.190///191/// As this function requires ownership on the `CompiledCodeStencil`, it gives it back at the end192/// of the function call. The value is left untouched.193pub fn serialize_compiled(194result: CompiledCodeStencil,195) -> (CompiledCodeStencil, Result<Vec<u8>, postcard::Error>) {196let cached = CachedFunc {197version_marker: VersionMarker,198stencil: result,199};200let result = postcard::to_allocvec(&cached);201(cached.stencil, result)202}203204/// An error returned when recompiling failed.205#[derive(Debug)]206pub enum RecompileError {207/// The version embedded in the cache entry isn't the same as cranelift's current version.208VersionMismatch,209/// An error occurred while deserializing the cache entry.210Deserialize(postcard::Error),211}212213impl fmt::Display for RecompileError {214fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {215match self {216RecompileError::VersionMismatch => write!(f, "cranelift version mismatch",),217RecompileError::Deserialize(err) => {218write!(f, "postcard failed during deserialization: {err}")219}220}221}222}223224/// Given a function that's been precompiled and its entry in the caching storage, try to shortcut225/// compilation of the given function.226///227/// Precondition: the bytes must have retrieved from a cache store entry which hash value228/// is strictly the same as the `Function`'s computed hash retrieved from `compute_cache_key`.229pub fn try_finish_recompile(func: &Function, bytes: &[u8]) -> Result<CompiledCode, RecompileError> {230match postcard::from_bytes::<CachedFunc>(bytes) {231Ok(result) => {232if result.version_marker != func.stencil.version_marker {233Err(RecompileError::VersionMismatch)234} else {235Ok(result.stencil.apply_params(&func.params))236}237}238Err(err) => Err(RecompileError::Deserialize(err)),239}240}241242243