Skip to main content

wasmtime_provider/
builder.rs

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