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