1use crate::{OptimizationLevel, eyre};
4use alloy_primitives::B256;
5use dashmap::DashMap;
6use revm_primitives::hardfork::SpecId;
7use std::{fs, path::PathBuf};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct RuntimeCacheKey {
12 pub code_hash: B256,
14 pub spec_id: SpecId,
16}
17
18#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct ArtifactKey {
23 pub runtime: RuntimeCacheKey,
25 pub backend: BackendSelection,
27 pub opt_level: OptimizationLevel,
29}
30
31#[derive(Clone, Debug)]
33pub struct StoredArtifact {
34 pub manifest: ArtifactManifest,
36 pub dylib_path: PathBuf,
38}
39
40#[derive(Clone, Debug)]
42pub struct ArtifactManifest {
43 pub artifact_key: ArtifactKey,
45 pub symbol_name: String,
47 pub bytecode_len: usize,
49 pub artifact_len: usize,
51 pub created_at_unix_secs: u64,
53 pub content_hash: [u8; 32],
55}
56
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
59pub enum BackendSelection {
60 #[default]
62 Auto,
63 Llvm,
65}
66
67pub trait ArtifactStore: Send + Sync + 'static {
72 fn load_all(&self) -> eyre::Result<Vec<(ArtifactKey, StoredArtifact)>>;
74
75 fn load(&self, key: &ArtifactKey) -> eyre::Result<Option<StoredArtifact>>;
77
78 fn store(
81 &self,
82 key: &ArtifactKey,
83 manifest: &ArtifactManifest,
84 dylib_bytes: &[u8],
85 ) -> eyre::Result<()>;
86
87 fn delete(&self, key: &ArtifactKey) -> eyre::Result<()>;
89
90 fn clear(&self) -> eyre::Result<()>;
92}
93
94#[derive(Debug)]
96pub struct RuntimeArtifactStore {
97 dir: tempfile::TempDir,
98 artifacts: DashMap<ArtifactKey, StoredArtifact>,
99}
100
101impl RuntimeArtifactStore {
102 pub fn new() -> eyre::Result<Self> {
104 Ok(Self { dir: tempfile::tempdir()?, artifacts: DashMap::default() })
105 }
106
107 pub fn len(&self) -> usize {
109 self.artifacts.len()
110 }
111
112 pub fn is_empty(&self) -> bool {
114 self.len() == 0
115 }
116
117 fn artifact_path(&self, key: &ArtifactKey) -> PathBuf {
118 self.dir.path().join(format!(
119 "{:x}_{:?}_{:?}_{:?}.so",
120 key.runtime.code_hash, key.runtime.spec_id, key.backend, key.opt_level,
121 ))
122 }
123}
124
125impl ArtifactStore for RuntimeArtifactStore {
126 fn load_all(&self) -> eyre::Result<Vec<(ArtifactKey, StoredArtifact)>> {
127 Ok(self
128 .artifacts
129 .iter()
130 .map(|entry| (entry.key().clone(), entry.value().clone()))
131 .collect())
132 }
133
134 fn load(&self, key: &ArtifactKey) -> eyre::Result<Option<StoredArtifact>> {
135 Ok(self.artifacts.get(key).map(|entry| entry.value().clone()))
136 }
137
138 fn store(
139 &self,
140 key: &ArtifactKey,
141 manifest: &ArtifactManifest,
142 dylib_bytes: &[u8],
143 ) -> eyre::Result<()> {
144 let path = self.artifact_path(key);
145 fs::write(&path, dylib_bytes)?;
146 self.artifacts
147 .insert(key.clone(), StoredArtifact { manifest: manifest.clone(), dylib_path: path });
148 Ok(())
149 }
150
151 fn delete(&self, key: &ArtifactKey) -> eyre::Result<()> {
152 if let Some((_, artifact)) = self.artifacts.remove(key) {
153 let _ = fs::remove_file(artifact.dylib_path);
154 }
155 Ok(())
156 }
157
158 fn clear(&self) -> eyre::Result<()> {
159 let paths = self.artifacts.iter().map(|entry| entry.dylib_path.clone()).collect::<Vec<_>>();
160 self.artifacts.clear();
161 for path in paths {
162 let _ = fs::remove_file(path);
163 }
164 Ok(())
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::{
171 ArtifactKey, ArtifactManifest, ArtifactStore, BackendSelection, RuntimeArtifactStore,
172 RuntimeCacheKey,
173 };
174 use alloy_primitives::B256;
175 use revm_primitives::hardfork::SpecId;
176 use revmc_backend::OptimizationLevel;
177
178 fn artifact_key(code_hash: B256) -> ArtifactKey {
179 ArtifactKey {
180 runtime: RuntimeCacheKey { code_hash, spec_id: SpecId::OSAKA },
181 backend: BackendSelection::Llvm,
182 opt_level: OptimizationLevel::Default,
183 }
184 }
185
186 fn manifest(key: ArtifactKey, artifact_len: usize) -> ArtifactManifest {
187 ArtifactManifest {
188 artifact_key: key,
189 symbol_name: "main".to_string(),
190 bytecode_len: 3,
191 artifact_len,
192 created_at_unix_secs: 42,
193 content_hash: [7; 32],
194 }
195 }
196
197 #[test]
198 fn runtime_artifact_store_starts_empty() {
199 let store = RuntimeArtifactStore::new().unwrap();
200
201 assert!(store.is_empty());
202 assert_eq!(store.len(), 0);
203 assert!(store.load_all().unwrap().is_empty());
204 }
205
206 #[test]
207 fn runtime_artifact_store_round_trips_artifacts() {
208 let store = RuntimeArtifactStore::new().unwrap();
209 let key = artifact_key(B256::with_last_byte(1));
210 let manifest = manifest(key.clone(), 4);
211
212 store.store(&key, &manifest, b"dylib").unwrap();
213
214 assert_eq!(store.len(), 1);
215 let loaded = store.load(&key).unwrap().unwrap();
216 assert_eq!(loaded.manifest.symbol_name, "main");
217 assert_eq!(loaded.manifest.artifact_len, 4);
218 assert_eq!(std::fs::read(&loaded.dylib_path).unwrap(), b"dylib");
219
220 let all = store.load_all().unwrap();
221 assert_eq!(all.len(), 1);
222 assert_eq!(all[0].0, key);
223 assert_eq!(all[0].1.manifest.content_hash, [7; 32]);
224 }
225
226 #[test]
227 fn runtime_artifact_store_replaces_artifacts() {
228 let store = RuntimeArtifactStore::new().unwrap();
229 let key = artifact_key(B256::with_last_byte(2));
230
231 store.store(&key, &manifest(key.clone(), 3), b"one").unwrap();
232 store.store(&key, &manifest(key.clone(), 5), b"three").unwrap();
233
234 assert_eq!(store.len(), 1);
235 let loaded = store.load(&key).unwrap().unwrap();
236 assert_eq!(loaded.manifest.artifact_len, 5);
237 assert_eq!(std::fs::read(&loaded.dylib_path).unwrap(), b"three");
238 }
239
240 #[test]
241 fn runtime_artifact_store_delete_and_clear_remove_files() {
242 let store = RuntimeArtifactStore::new().unwrap();
243 let first = artifact_key(B256::with_last_byte(3));
244 let second = artifact_key(B256::with_last_byte(4));
245
246 store.store(&first, &manifest(first.clone(), 1), b"a").unwrap();
247 store.store(&second, &manifest(second.clone(), 1), b"b").unwrap();
248 let first_path = store.load(&first).unwrap().unwrap().dylib_path;
249 let second_path = store.load(&second).unwrap().unwrap().dylib_path;
250
251 store.delete(&first).unwrap();
252 assert!(store.load(&first).unwrap().is_none());
253 assert!(!first_path.exists());
254 assert!(second_path.exists());
255
256 store.clear().unwrap();
257 assert!(store.is_empty());
258 assert!(!second_path.exists());
259 }
260
261 #[test]
262 fn backend_selection_defaults_to_auto() {
263 assert_eq!(BackendSelection::default(), BackendSelection::Auto);
264 }
265}