Skip to main content

wasmtime_provider/
builder.rs

1use crate::errors::{Error, Result};
2use crate::{EpochDeadlines, ResourceLimits, WasmtimeEngineProvider, WasmtimeEngineProviderPre};
3#[cfg(feature = "async")]
4use crate::{WasmtimeEngineProviderAsync, WasmtimeEngineProviderAsyncPre};
5
6/// Used to build [`WasmtimeEngineProvider`](crate::WasmtimeEngineProvider) instances.
7#[allow(missing_debug_implementations)]
8#[derive(Default)]
9pub struct WasmtimeEngineProviderBuilder<'a> {
10  engine: Option<wasmtime::Engine>,
11  module: Option<wasmtime::Module>,
12  module_bytes: Option<&'a [u8]>,
13  #[cfg(feature = "cache")]
14  cache_enabled: bool,
15  #[cfg(feature = "cache")]
16  cache_path: Option<std::path::PathBuf>,
17  #[cfg(feature = "wasi")]
18  wasi_params: Option<wapc::WasiParams>,
19  epoch_deadlines: Option<EpochDeadlines>,
20  resource_limits: Option<ResourceLimits>,
21}
22
23#[allow(deprecated)]
24impl<'a> WasmtimeEngineProviderBuilder<'a> {
25  /// Create a builder instance
26  #[must_use]
27  pub fn new() -> Self {
28    Default::default()
29  }
30
31  /// Provide contents of the WebAssembly module
32  #[must_use]
33  pub fn module_bytes(mut self, module_bytes: &'a [u8]) -> Self {
34    self.module_bytes = Some(module_bytes);
35    self
36  }
37
38  /// Provide a preloaded [`wasmtime::Module`]
39  ///
40  /// **Warning:** the [`wasmtime::Engine`] used to load it must be provided via the
41  /// [`WasmtimeEngineProviderBuilder::engine`] method, otherwise the code
42  /// will panic at runtime later.
43  #[must_use]
44  pub fn module(mut self, module: wasmtime::Module) -> Self {
45    self.module = Some(module);
46    self
47  }
48
49  /// Provide a preinitialized [`wasmtime::Engine`]
50  ///
51  /// **Warning:** when used, engine specific options like
52  /// [`cache`](WasmtimeEngineProviderBuilder::enable_cache) and
53  /// [`enable_epoch_interruptions`](WasmtimeEngineProviderBuilder::enable_epoch_interruptions)
54  /// must be pre-configured by the user. `WasmtimeEngineProviderBuilder` won't be
55  /// able to configure them at [`build`](WasmtimeEngineProviderBuilder::build) time.
56  #[must_use]
57  pub fn engine(mut self, engine: wasmtime::Engine) -> Self {
58    self.engine = Some(engine);
59    self
60  }
61
62  /// WASI params
63  #[cfg(feature = "wasi")]
64  #[cfg_attr(docsrs, doc(cfg(feature = "wasi")))]
65  #[must_use]
66  pub fn wasi_params(mut self, wasi: wapc::WasiParams) -> Self {
67    self.wasi_params = Some(wasi);
68    self
69  }
70
71  /// Enable Wasmtime cache feature
72  ///
73  /// **Warning:** this has no effect when a custom [`wasmtime::Engine`] is provided via
74  /// the [`WasmtimeEngineProviderBuilder::engine`] helper. In that case, it's up to the
75  /// user to provide a [`wasmtime::Engine`] instance with the cache values properly configured.
76  #[cfg(feature = "cache")]
77  #[cfg_attr(docsrs, doc(cfg(feature = "cache")))]
78  #[must_use]
79  pub fn enable_cache(mut self, path: Option<&std::path::Path>) -> Self {
80    self.cache_enabled = true;
81    self.cache_path = path.map(|p| p.to_path_buf());
82    self
83  }
84
85  /// Enable Wasmtime [epoch-based interruptions](wasmtime::Config::epoch_interruption) and set
86  /// the deadlines to be enforced.
87  ///
88  /// **Warning:** when providing an instance of `wasmtime::Engine` via the
89  /// `WasmtimeEngineProvider::engine` helper, ensure the `wasmtime::Engine`
90  /// has been created with the `epoch_interruption` feature enabled
91  #[must_use]
92  pub fn enable_epoch_interruptions(mut self, epoch_deadlines: EpochDeadlines) -> Self {
93    self.epoch_deadlines = Some(epoch_deadlines);
94    self
95  }
96
97  /// Enable enforcement of resource limits on the instantiated WebAssembly
98  /// module, leveraging wasmtime's [`ResourceLimiter`](wasmtime::ResourceLimiter)
99  /// facility.
100  ///
101  /// This can be used to prevent a malicious, or misbehaving, WebAssembly
102  /// module from exhausting the host's memory. See [`ResourceLimits`] for
103  /// details.
104  #[must_use]
105  pub fn enable_resource_limits(mut self, resource_limits: ResourceLimits) -> Self {
106    self.resource_limits = Some(resource_limits);
107    self
108  }
109
110  /// Create a [`WasmtimeEngineProviderPre`] instance. This instance can then
111  /// be reused as many time as wanted to quickly instantiate a [`WasmtimeEngineProvider`]
112  /// by using the [`WasmtimeEngineProviderPre::rehydrate`] method.
113  pub fn build_pre(&self) -> Result<WasmtimeEngineProviderPre> {
114    if self.module_bytes.is_some() && self.module.is_some() {
115      return Err(Error::BuilderInvalidConfig(
116        "`module_bytes` and `module` cannot be provided at the same time".to_owned(),
117      ));
118    }
119    if self.module_bytes.is_none() && self.module.is_none() {
120      return Err(Error::BuilderInvalidConfig(
121        "Neither `module_bytes` nor `module` have been provided".to_owned(),
122      ));
123    }
124
125    let pre = match &self.engine {
126      Some(e) => {
127        let module = self.module_bytes.as_ref().map_or_else(
128          || Ok(self.module.as_ref().unwrap().clone()),
129          |module_bytes| wasmtime::Module::new(e, module_bytes),
130        )?;
131
132        // note: we have to call `.clone()` because `e` is behind
133        // a shared reference and `Engine` does not implement `Copy`.
134        // However, cloning an `Engine` is a cheap operation because
135        // under the hood wasmtime does not create a new `Engine`, but
136        // rather creates a new reference to it.
137        // See https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html#engines-and-clone
138        cfg_if::cfg_if! {
139            if #[cfg(feature = "wasi")] {
140                WasmtimeEngineProviderPre::new(e.clone(), module, self.wasi_params.clone(), self.resource_limits)
141            } else {
142                WasmtimeEngineProviderPre::new(e.clone(), module, self.resource_limits)
143            }
144        }
145      }
146      None => {
147        let mut config = wasmtime::Config::default();
148        if self.epoch_deadlines.is_some() {
149          config.epoch_interruption(true);
150        }
151
152        cfg_if::cfg_if! {
153            if #[cfg(feature = "cache")] {
154                if self.cache_enabled {
155                    config.strategy(wasmtime::Strategy::Cranelift);
156                    let cache = self.cache_path.as_ref().map_or_else(
157                        || wasmtime::CacheConfig::from_file(None).and_then(wasmtime::Cache::new),
158                        |cache_path| {
159                            let mut cache_config = wasmtime::CacheConfig::new();
160                            cache_config.with_directory(cache_path);
161                            wasmtime::Cache::new(cache_config)
162                        }
163                    ).map_or_else(
164                        |e| {
165                            log::warn!("Wasmtime cache configuration not found ({e}). Repeated loads will speed up significantly with a cache configuration. See https://docs.wasmtime.dev/cli-cache.html for more information.");
166                            None
167                        },
168                        Some,
169                    );
170                    config.cache(cache);
171                }
172            }
173        }
174
175        let engine = wasmtime::Engine::new(&config)?;
176
177        let module = self.module_bytes.as_ref().map_or_else(
178          || Ok(self.module.as_ref().unwrap().clone()),
179          |module_bytes| wasmtime::Module::new(&engine, module_bytes),
180        )?;
181
182        cfg_if::cfg_if! {
183            if #[cfg(feature = "wasi")] {
184                WasmtimeEngineProviderPre::new(engine, module, self.wasi_params.clone(), self.resource_limits)
185            } else {
186                WasmtimeEngineProviderPre::new(engine, module, self.resource_limits)
187
188            }
189        }
190      }
191    }?;
192
193    Ok(pre)
194  }
195
196  /// Create a `WasmtimeEngineProvider` instance
197  pub fn build(&self) -> Result<WasmtimeEngineProvider> {
198    let pre = self.build_pre()?;
199    pre.rehydrate(self.epoch_deadlines)
200  }
201
202  /// Create a [`WasmtimeEngineProviderAsyncPre`] instance. This instance can then
203  /// be reused as many time as wanted to quickly instantiate a [`WasmtimeEngineProviderAsync`]
204  /// by using the [`WasmtimeEngineProviderAsyncPre::rehydrate`] method.
205  ///
206  /// **Warning:** if provided by the user, the [`wasmtime::Engine`] must have been
207  /// created with async support enabled otherwise the code will panic at runtime.
208  #[cfg(feature = "async")]
209  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
210  pub fn build_async_pre(&self) -> Result<WasmtimeEngineProviderAsyncPre> {
211    if self.module_bytes.is_some() && self.module.is_some() {
212      return Err(Error::BuilderInvalidConfig(
213        "`module_bytes` and `module` cannot be provided at the same time".to_owned(),
214      ));
215    }
216    if self.module_bytes.is_none() && self.module.is_none() {
217      return Err(Error::BuilderInvalidConfig(
218        "Neither `module_bytes` nor `module` have been provided".to_owned(),
219      ));
220    }
221
222    let pre = match &self.engine {
223      Some(e) => {
224        let module = self.module_bytes.as_ref().map_or_else(
225          || Ok(self.module.as_ref().unwrap().clone()),
226          |module_bytes| wasmtime::Module::new(e, module_bytes),
227        )?;
228
229        // note: we have to call `.clone()` because `e` is behind
230        // a shared reference and `Engine` does not implement `Copy`.
231        // However, cloning an `Engine` is a cheap operation because
232        // under the hood wasmtime does not create a new `Engine`, but
233        // rather creates a new reference to it.
234        // See https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html#engines-and-clone
235        cfg_if::cfg_if! {
236            if #[cfg(feature = "wasi")] {
237                WasmtimeEngineProviderAsyncPre::new(e.clone(), module, self.wasi_params.clone(), self.epoch_deadlines, self.resource_limits)
238            } else {
239                WasmtimeEngineProviderAsyncPre::new(e.clone(), module, self.epoch_deadlines, self.resource_limits)
240            }
241        }
242      }
243      None => {
244        let mut config = wasmtime::Config::default();
245        config.async_support(true);
246
247        if self.epoch_deadlines.is_some() {
248          config.epoch_interruption(true);
249        }
250
251        cfg_if::cfg_if! {
252            if #[cfg(feature = "cache")] {
253                  if self.cache_enabled {
254                    config.strategy(wasmtime::Strategy::Cranelift);
255                    let cache = self.cache_path.as_ref().map_or_else(
256                        || wasmtime::CacheConfig::from_file(None).and_then(wasmtime::Cache::new),
257                        |cache_path| {
258                            let mut cache_config = wasmtime::CacheConfig::new();
259                            cache_config.with_directory(cache_path);
260                            wasmtime::Cache::new(cache_config)
261                        }
262                    ).map_or_else(
263                        |e| {
264                            log::warn!("Wasmtime cache configuration not found ({e}). Repeated loads will speed up significantly with a cache configuration. See https://docs.wasmtime.dev/cli-cache.html for more information.");
265                            None
266                        },
267                        Some,
268                    );
269                    config.cache(cache);
270                }
271            }
272        }
273
274        let engine = wasmtime::Engine::new(&config)?;
275
276        let module = self.module_bytes.as_ref().map_or_else(
277          || Ok(self.module.as_ref().unwrap().clone()),
278          |module_bytes| wasmtime::Module::new(&engine, module_bytes),
279        )?;
280
281        cfg_if::cfg_if! {
282            if #[cfg(feature = "wasi")] {
283                WasmtimeEngineProviderAsyncPre::new(engine, module, self.wasi_params.clone(), self.epoch_deadlines, self.resource_limits)
284            } else {
285                WasmtimeEngineProviderAsyncPre::new(engine, module, self.epoch_deadlines, self.resource_limits)
286            }
287        }
288      }
289    }?;
290
291    Ok(pre)
292  }
293
294  /// Create a `WasmtimeEngineProviderAsync` instance
295  #[cfg(feature = "async")]
296  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
297  pub fn build_async(&self) -> Result<WasmtimeEngineProviderAsync> {
298    let pre = self.build_async_pre()?;
299    pre.rehydrate()
300  }
301}