wasmer_runtime_unc/cache.rs
1//! The cache module provides the common data structures used by compiler backends to allow
2//! serializing compiled wasm code to a binary format. The binary format can be persisted,
3//! and loaded to allow skipping compilation and fast startup.
4
5use crate::Module;
6use memmap::Mmap;
7use std::{
8 fmt,
9 fs::{create_dir_all, File},
10 io::{self, Write},
11 path::PathBuf,
12};
13
14pub use super::Backend;
15use wasmer_runtime_core::cache::Error as CacheError;
16pub use wasmer_runtime_core::cache::{Artifact, WasmHash};
17
18/// A generic cache for storing and loading compiled wasm modules.
19///
20/// The `wasmer-runtime` supplies a naive `FileSystemCache` api.
21pub trait Cache {
22 /// Error type to return when load error occurs
23 type LoadError: fmt::Debug;
24 /// Error type to return when store error occurs
25 type StoreError: fmt::Debug;
26
27 /// loads a module using the default `Backend`
28 fn load(&self, key: WasmHash) -> Result<Module, Self::LoadError>;
29 /// loads a cached module using a specific `Backend`
30 fn load_with_backend(&self, key: WasmHash, backend: Backend)
31 -> Result<Module, Self::LoadError>;
32 /// Store a module into the cache with the given key
33 fn store(&mut self, key: WasmHash, module: Module) -> Result<(), Self::StoreError>;
34}
35
36/// Representation of a directory that contains compiled wasm artifacts.
37///
38/// The `FileSystemCache` type implements the [`Cache`] trait, which allows it to be used
39/// generically when some sort of cache is required.
40///
41/// [`Cache`]: trait.Cache.html
42///
43/// # Usage:
44///
45/// ```rust
46/// use wasmer_runtime::cache::{Cache, FileSystemCache, WasmHash};
47///
48/// # use wasmer_runtime::{Module, error::CacheError};
49/// fn store_module(module: Module) -> Result<Module, CacheError> {
50/// // Create a new file system cache.
51/// // This is unsafe because we can't ensure that the artifact wasn't
52/// // corrupted or tampered with.
53/// let mut fs_cache = unsafe { FileSystemCache::new("some/directory/goes/here")? };
54/// // Compute a key for a given WebAssembly binary
55/// let key = WasmHash::generate(&[]);
56/// // Store a module into the cache given a key
57/// fs_cache.store(key, module.clone())?;
58/// Ok(module)
59/// }
60/// ```
61pub struct FileSystemCache {
62 path: PathBuf,
63}
64
65impl FileSystemCache {
66 /// Construct a new `FileSystemCache` around the specified directory.
67 /// The contents of the cache are stored in sub-versioned directories.
68 ///
69 /// # Note:
70 /// This method is unsafe because there's no way to ensure the artifacts
71 /// stored in this cache haven't been corrupted or tampered with.
72 pub unsafe fn new<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
73 let path: PathBuf = path.into();
74 if path.exists() {
75 let metadata = path.metadata()?;
76 if metadata.is_dir() {
77 if !metadata.permissions().readonly() {
78 Ok(Self { path })
79 } else {
80 // This directory is readonly.
81 Err(io::Error::new(
82 io::ErrorKind::PermissionDenied,
83 format!("the supplied path is readonly: {}", path.display()),
84 ))
85 }
86 } else {
87 // This path points to a file.
88 Err(io::Error::new(
89 io::ErrorKind::PermissionDenied,
90 format!(
91 "the supplied path already points to a file: {}",
92 path.display()
93 ),
94 ))
95 }
96 } else {
97 // Create the directory and any parent directories if they don't yet exist.
98 create_dir_all(&path)?;
99 Ok(Self { path })
100 }
101 }
102}
103
104impl Cache for FileSystemCache {
105 type LoadError = CacheError;
106 type StoreError = CacheError;
107
108 fn load(&self, key: WasmHash) -> Result<Module, CacheError> {
109 self.load_with_backend(key, Backend::default())
110 }
111
112 fn load_with_backend(&self, key: WasmHash, backend: Backend) -> Result<Module, CacheError> {
113 let filename = key.encode();
114 let mut new_path_buf = self.path.clone();
115 new_path_buf.push(backend.to_string());
116 new_path_buf.push(filename);
117 let file = File::open(new_path_buf)?;
118 let mmap = unsafe { Mmap::map(&file)? };
119
120 let serialized_cache = Artifact::deserialize(&mmap[..])?;
121 unsafe {
122 wasmer_runtime_core::load_cache_with(
123 serialized_cache,
124 crate::compiler_for_backend(backend)
125 .ok_or_else(|| CacheError::UnsupportedBackend(backend.to_string().to_owned()))?
126 .as_ref(),
127 )
128 }
129 }
130
131 fn store(&mut self, key: WasmHash, module: Module) -> Result<(), CacheError> {
132 let filename = key.encode();
133 let backend_str = module.info().backend.to_string();
134 let mut new_path_buf = self.path.clone();
135 new_path_buf.push(backend_str);
136
137 let serialized_cache = module.cache()?;
138 let buffer = serialized_cache.serialize()?;
139
140 std::fs::create_dir_all(&new_path_buf)?;
141 new_path_buf.push(filename);
142 let mut file = File::create(new_path_buf)?;
143 file.write_all(&buffer)?;
144
145 Ok(())
146 }
147}