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}