wasmtime_provider/
builder.rs

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