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