wasmtime_provider/
builder.rs

1use crate::errors::{Error, Result};
2use crate::{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<crate::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  /// Two kind of deadlines have to be set:
88  ///
89  /// * `wapc_init_deadline`: the number of ticks the waPC initialization code can take before the
90  ///   code is interrupted. This is the code usually defined inside of the `wapc_init`/`_start`
91  ///   functions
92  /// * `wapc_func_deadline`: the number of ticks any regular waPC guest function can run before
93  ///   its terminated by the host
94  ///
95  /// Both these limits are expressed using the number of ticks that are allowed before the
96  /// WebAssembly execution is interrupted.
97  /// It's up to the embedder of waPC to define how much time a single tick is granted. This could
98  /// be 1 second, 10 nanoseconds, or whatever the user prefers.
99  ///
100  /// **Warning:** when providing an instance of `wasmtime::Engine` via the
101  /// `WasmtimeEngineProvider::engine` helper, ensure the `wasmtime::Engine`
102  /// has been created with the `epoch_interruption` feature enabled
103  #[must_use]
104  pub fn enable_epoch_interruptions(mut self, wapc_init_deadline: u64, wapc_func_deadline: u64) -> Self {
105    self.epoch_deadlines = Some(crate::EpochDeadlines {
106      wapc_init: wapc_init_deadline,
107      wapc_func: wapc_func_deadline,
108    });
109    self
110  }
111
112  /// Create a [`WasmtimeEngineProviderPre`] instance. This instance can then
113  /// be reused as many time as wanted to quickly instantiate a [`WasmtimeEngineProvider`]
114  /// by using the [`WasmtimeEngineProviderPre::rehydrate`] method.
115  pub fn build_pre(&self) -> Result<WasmtimeEngineProviderPre> {
116    if self.module_bytes.is_some() && self.module.is_some() {
117      return Err(Error::BuilderInvalidConfig(
118        "`module_bytes` and `module` cannot be provided at the same time".to_owned(),
119      ));
120    }
121    if self.module_bytes.is_none() && self.module.is_none() {
122      return Err(Error::BuilderInvalidConfig(
123        "Neither `module_bytes` nor `module` have been provided".to_owned(),
124      ));
125    }
126
127    let pre = match &self.engine {
128      Some(e) => {
129        let module = self.module_bytes.as_ref().map_or_else(
130          || Ok(self.module.as_ref().unwrap().clone()),
131          |module_bytes| wasmtime::Module::new(e, module_bytes),
132        )?;
133
134        // note: we have to call `.clone()` because `e` is behind
135        // a shared reference and `Engine` does not implement `Copy`.
136        // However, cloning an `Engine` is a cheap operation because
137        // under the hood wasmtime does not create a new `Engine`, but
138        // rather creates a new reference to it.
139        // See https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html#engines-and-clone
140        cfg_if::cfg_if! {
141            if #[cfg(feature = "wasi")] {
142                WasmtimeEngineProviderPre::new(e.clone(), module, self.wasi_params.clone(), self.epoch_deadlines)
143            } else {
144                WasmtimeEngineProviderPre::new(e.clone(), module, self.epoch_deadlines)
145            }
146        }
147      }
148      None => {
149        let mut config = wasmtime::Config::default();
150        if self.epoch_deadlines.is_some() {
151          config.epoch_interruption(true);
152        }
153
154        cfg_if::cfg_if! {
155            if #[cfg(feature = "cache")] {
156                if self.cache_enabled {
157                    config.strategy(wasmtime::Strategy::Cranelift);
158                    let cache = self.cache_path.as_ref().map_or_else(
159                        || wasmtime::CacheConfig::from_file(None).and_then(wasmtime::Cache::new),
160                        |cache_path| {
161                            let mut cache_config = wasmtime::CacheConfig::new();
162                            cache_config.with_directory(cache_path);
163                            wasmtime::Cache::new(cache_config)
164                        }
165                    ).map_or_else(
166                        |e| {
167                            log::warn!("Wasmtime cache configuration not found ({}). Repeated loads will speed up significantly with a cache configuration. See https://docs.wasmtime.dev/cli-cache.html for more information.",e);
168                            None
169                        },
170                        Some,
171                    );
172                    config.cache(cache);
173                }
174            }
175        }
176
177        let engine = wasmtime::Engine::new(&config)?;
178
179        let module = self.module_bytes.as_ref().map_or_else(
180          || Ok(self.module.as_ref().unwrap().clone()),
181          |module_bytes| wasmtime::Module::new(&engine, module_bytes),
182        )?;
183
184        cfg_if::cfg_if! {
185            if #[cfg(feature = "wasi")] {
186                WasmtimeEngineProviderPre::new(engine, module, self.wasi_params.clone(), self.epoch_deadlines)
187            } else {
188                WasmtimeEngineProviderPre::new(engine, module, self.epoch_deadlines)
189
190            }
191        }
192      }
193    }?;
194
195    Ok(pre)
196  }
197
198  /// Create a `WasmtimeEngineProvider` instance
199  pub fn build(&self) -> Result<WasmtimeEngineProvider> {
200    let pre = self.build_pre()?;
201    pre.rehydrate()
202  }
203
204  /// Create a [`WasmtimeEngineProviderAsyncPre`] instance. This instance can then
205  /// be reused as many time as wanted to quickly instantiate a [`WasmtimeEngineProviderAsync`]
206  /// by using the [`WasmtimeEngineProviderAsyncPre::rehydrate`] method.
207  ///
208  /// **Warning:** if provided by the user, the [`wasmtime::Engine`] must have been
209  /// created with async support enabled otherwise the code will panic at runtime.
210  #[cfg(feature = "async")]
211  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
212  pub fn build_async_pre(&self) -> Result<WasmtimeEngineProviderAsyncPre> {
213    if self.module_bytes.is_some() && self.module.is_some() {
214      return Err(Error::BuilderInvalidConfig(
215        "`module_bytes` and `module` cannot be provided at the same time".to_owned(),
216      ));
217    }
218    if self.module_bytes.is_none() && self.module.is_none() {
219      return Err(Error::BuilderInvalidConfig(
220        "Neither `module_bytes` nor `module` have been provided".to_owned(),
221      ));
222    }
223
224    let pre = match &self.engine {
225      Some(e) => {
226        let module = self.module_bytes.as_ref().map_or_else(
227          || Ok(self.module.as_ref().unwrap().clone()),
228          |module_bytes| wasmtime::Module::new(e, module_bytes),
229        )?;
230
231        // note: we have to call `.clone()` because `e` is behind
232        // a shared reference and `Engine` does not implement `Copy`.
233        // However, cloning an `Engine` is a cheap operation because
234        // under the hood wasmtime does not create a new `Engine`, but
235        // rather creates a new reference to it.
236        // See https://docs.rs/wasmtime/latest/wasmtime/struct.Engine.html#engines-and-clone
237        cfg_if::cfg_if! {
238            if #[cfg(feature = "wasi")] {
239                WasmtimeEngineProviderAsyncPre::new(e.clone(), module, self.wasi_params.clone(), self.epoch_deadlines)
240            } else {
241                WasmtimeEngineProviderAsyncPre::new(e.clone(), module, self.epoch_deadlines)
242            }
243        }
244      }
245      None => {
246        let mut config = wasmtime::Config::default();
247        config.async_support(true);
248
249        if self.epoch_deadlines.is_some() {
250          config.epoch_interruption(true);
251        }
252
253        cfg_if::cfg_if! {
254            if #[cfg(feature = "cache")] {
255                  if self.cache_enabled {
256                    config.strategy(wasmtime::Strategy::Cranelift);
257                    let cache = self.cache_path.as_ref().map_or_else(
258                        || wasmtime::CacheConfig::from_file(None).and_then(wasmtime::Cache::new),
259                        |cache_path| {
260                            let mut cache_config = wasmtime::CacheConfig::new();
261                            cache_config.with_directory(cache_path);
262                            wasmtime::Cache::new(cache_config)
263                        }
264                    ).map_or_else(
265                        |e| {
266                            log::warn!("Wasmtime cache configuration not found ({}). Repeated loads will speed up significantly with a cache configuration. See https://docs.wasmtime.dev/cli-cache.html for more information.",e);
267                            None
268                        },
269                        Some,
270                    );
271                    config.cache(cache);
272                }
273            }
274        }
275
276        let engine = wasmtime::Engine::new(&config)?;
277
278        let module = self.module_bytes.as_ref().map_or_else(
279          || Ok(self.module.as_ref().unwrap().clone()),
280          |module_bytes| wasmtime::Module::new(&engine, module_bytes),
281        )?;
282
283        cfg_if::cfg_if! {
284            if #[cfg(feature = "wasi")] {
285                WasmtimeEngineProviderAsyncPre::new(engine, module, self.wasi_params.clone(), self.epoch_deadlines)
286            } else {
287                WasmtimeEngineProviderAsyncPre::new(engine, module, self.epoch_deadlines)
288            }
289        }
290      }
291    }?;
292
293    Ok(pre)
294  }
295
296  /// Create a `WasmtimeEngineProviderAsync` instance
297  #[cfg(feature = "async")]
298  #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
299  pub fn build_async(&self) -> Result<WasmtimeEngineProviderAsync> {
300    let pre = self.build_async_pre()?;
301    pre.rehydrate()
302  }
303}