Skip to main content

revmc_runtime/runtime/
storage.rs

1//! Artifact storage trait and data model.
2
3use crate::{OptimizationLevel, eyre};
4use alloy_primitives::B256;
5use dashmap::DashMap;
6use revm_primitives::hardfork::SpecId;
7use std::{fs, path::PathBuf};
8
9/// Runtime cache key: the minimal identity for a compiled program at runtime.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
11pub struct RuntimeCacheKey {
12    /// The code hash of the contract bytecode.
13    pub code_hash: B256,
14    /// The EVM spec (hardfork) the code was compiled against.
15    pub spec_id: SpecId,
16}
17
18/// Full artifact identity for persisted artifacts.
19///
20/// Persisted artifacts must match all fields of this key to be loaded.
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
22pub struct ArtifactKey {
23    /// The runtime cache key (code_hash + spec_id).
24    pub runtime: RuntimeCacheKey,
25    /// The compiler backend used.
26    pub backend: BackendSelection,
27    /// The optimization level used.
28    pub opt_level: OptimizationLevel,
29}
30
31/// A stored artifact consisting of a manifest and a path to the compiled dylib.
32#[derive(Clone, Debug)]
33pub struct StoredArtifact {
34    /// Metadata about the artifact.
35    pub manifest: ArtifactManifest,
36    /// Path to the shared library on disk. The store owns and manages these files.
37    pub dylib_path: PathBuf,
38}
39
40/// Metadata for a stored artifact.
41#[derive(Clone, Debug)]
42pub struct ArtifactManifest {
43    /// The full artifact key.
44    pub artifact_key: ArtifactKey,
45    /// The symbol name to look up in the loaded library.
46    pub symbol_name: String,
47    /// Length of the original bytecode.
48    pub bytecode_len: usize,
49    /// Length of the compiled artifact in bytes.
50    pub artifact_len: usize,
51    /// Creation timestamp (unix seconds).
52    pub created_at_unix_secs: u64,
53    /// Keccak-256 digest of the dylib bytes.
54    pub content_hash: [u8; 32],
55}
56
57/// Backend selection for compilation.
58#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
59pub enum BackendSelection {
60    /// Automatically select the best available backend.
61    #[default]
62    Auto,
63    /// Use the LLVM backend.
64    Llvm,
65}
66
67/// Trait for loading and storing compiled artifacts.
68///
69/// Implementations manage artifact files on the filesystem. The store owns the dylib files and
70/// returns paths to them. The backend loads shared libraries directly from these paths.
71pub trait ArtifactStore: Send + Sync + 'static {
72    /// Loads all available artifacts from storage.
73    fn load_all(&self) -> eyre::Result<Vec<(ArtifactKey, StoredArtifact)>>;
74
75    /// Loads a single artifact by key.
76    fn load(&self, key: &ArtifactKey) -> eyre::Result<Option<StoredArtifact>>;
77
78    /// Stores an artifact. The `dylib_bytes` are the raw shared-library bytes to persist.
79    /// The store writes them to disk and the returned path (via subsequent `load`) points there.
80    fn store(
81        &self,
82        key: &ArtifactKey,
83        manifest: &ArtifactManifest,
84        dylib_bytes: &[u8],
85    ) -> eyre::Result<()>;
86
87    /// Deletes an artifact by key.
88    fn delete(&self, key: &ArtifactKey) -> eyre::Result<()>;
89
90    /// Clears all stored artifacts.
91    fn clear(&self) -> eyre::Result<()>;
92}
93
94/// In-memory artifact index backed by dylib files in a temporary directory.
95#[derive(Debug)]
96pub struct RuntimeArtifactStore {
97    dir: tempfile::TempDir,
98    artifacts: DashMap<ArtifactKey, StoredArtifact>,
99}
100
101impl RuntimeArtifactStore {
102    /// Creates an empty temporary runtime artifact store.
103    pub fn new() -> eyre::Result<Self> {
104        Ok(Self { dir: tempfile::tempdir()?, artifacts: DashMap::default() })
105    }
106
107    /// Returns the number of artifacts tracked by this store.
108    pub fn len(&self) -> usize {
109        self.artifacts.len()
110    }
111
112    /// Returns whether this store contains no artifacts.
113    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}