dusk_wasmtime/compile/
code_builder.rs

1use crate::Engine;
2use anyhow::{anyhow, bail, Context, Result};
3use std::borrow::Cow;
4use std::path::Path;
5
6/// Builder-style structure used to create a [`Module`](crate::module::Module) or
7/// pre-compile a module to a serialized list of bytes.
8///
9/// This structure can be used for more advanced configuration when compiling a
10/// WebAssembly module. Most configuration can use simpler constructors such as:
11///
12/// * [`Module::new`](crate::Module::new)
13/// * [`Module::from_file`](crate::Module::from_file)
14/// * [`Module::from_binary`](crate::Module::from_binary)
15///
16/// Note that a [`CodeBuilder`] always involves compiling WebAssembly bytes
17/// to machine code. To deserialize a list of bytes use
18/// [`Module::deserialize`](crate::Module::deserialize) instead.
19///
20/// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured
21/// before calling [`compile_module_serialized`] or [`compile_module`]. This can be
22/// provided with either the [`wasm`] or [`wasm_file`] method. Note that only
23/// a single source of bytes can be provided.
24///
25/// # WebAssembly Text Format
26///
27/// This builder supports the WebAssembly Text Format (`*.wat` files).
28/// WebAssembly text files are automatically converted to a WebAssembly binary
29/// and then the binary is compiled. This requires the `wat` feature of the
30/// `wasmtime` crate to be enabled, and the feature is enabled by default.
31///
32/// If the text format is not desired then the [`CodeBuilder::wat`] method
33/// can be used to disable this conversion.
34///
35/// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
36/// [`compile_module`]: CodeBuilder::compile_module
37/// [`wasm`]: CodeBuilder::wasm
38/// [`wasm_file`]: CodeBuilder::wasm_file
39pub struct CodeBuilder<'a> {
40    pub(super) engine: &'a Engine,
41    wasm: Option<Cow<'a, [u8]>>,
42    wasm_path: Option<Cow<'a, Path>>,
43    wat: bool,
44}
45
46impl<'a> CodeBuilder<'a> {
47    /// Creates a new builder which will insert modules into the specified
48    /// [`Engine`].
49    pub fn new(engine: &'a Engine) -> CodeBuilder<'a> {
50        CodeBuilder {
51            engine,
52            wasm: None,
53            wasm_path: None,
54            wat: cfg!(feature = "wat"),
55        }
56    }
57
58    /// Configures the WebAssembly binary or text that is being compiled.
59    ///
60    /// The `wasm_bytes` parameter is either a binary WebAssembly file or a
61    /// WebAssembly module in its text format. This will be stored within the
62    /// [`CodeBuilder`] for processing later when compilation is finalized.
63    ///
64    /// The optional `wasm_path` parameter is the path to the `wasm_bytes` on
65    /// disk, if any. This may be used for diagnostics and other
66    /// debugging-related purposes, but this method will not read the path
67    /// specified.
68    ///
69    /// # Errors
70    ///
71    /// If wasm bytes have already been configured via a call to this method or
72    /// [`CodeBuilder::wasm_file`] then an error will be returned.
73    pub fn wasm(&mut self, wasm_bytes: &'a [u8], wasm_path: Option<&'a Path>) -> Result<&mut Self> {
74        if self.wasm.is_some() {
75            bail!("cannot call `wasm` or `wasm_file` twice");
76        }
77        self.wasm = Some(wasm_bytes.into());
78        self.wasm_path = wasm_path.map(|p| p.into());
79        Ok(self)
80    }
81
82    /// Configures whether the WebAssembly text format is supported in this
83    /// builder.
84    ///
85    /// This support is enabled by default if the `wat` crate feature is also
86    /// enabled.
87    ///
88    /// # Errors
89    ///
90    /// If this feature is explicitly enabled here via this method and the
91    /// `wat` crate feature is disabled then an error will be returned.
92    pub fn wat(&mut self, enable: bool) -> Result<&mut Self> {
93        if !cfg!(feature = "wat") && enable {
94            bail!("support for `wat` was disabled at compile time");
95        }
96        self.wat = enable;
97        Ok(self)
98    }
99
100    /// Reads the `file` specified for the WebAssembly bytes that are going to
101    /// be compiled.
102    ///
103    /// This method will read `file` from the filesystem and interpret it
104    /// either as a WebAssembly binary or as a WebAssembly text file. The
105    /// contents are inspected to do this, the file extension is not consulted.
106    ///
107    /// # Errors
108    ///
109    /// If wasm bytes have already been configured via a call to this method or
110    /// [`CodeBuilder::wasm`] then an error will be returned.
111    ///
112    /// If `file` can't be read or an error happens reading it then that will
113    /// also be returned.
114    pub fn wasm_file(&mut self, file: &'a Path) -> Result<&mut Self> {
115        if self.wasm.is_some() {
116            bail!("cannot call `wasm` or `wasm_file` twice");
117        }
118        let wasm = std::fs::read(file)
119            .with_context(|| format!("failed to read input file: {}", file.display()))?;
120        self.wasm = Some(wasm.into());
121        self.wasm_path = Some(file.into());
122        Ok(self)
123    }
124
125    pub(super) fn wasm_binary(&self) -> Result<Cow<'_, [u8]>> {
126        let wasm = self
127            .wasm
128            .as_ref()
129            .ok_or_else(|| anyhow!("no wasm bytes have been configured"))?;
130        if self.wat {
131            #[cfg(feature = "wat")]
132            return wat::parse_bytes(wasm).map_err(|mut e| {
133                if let Some(path) = &self.wasm_path {
134                    e.set_path(path);
135                }
136                e.into()
137            });
138        }
139        Ok((&wasm[..]).into())
140    }
141
142    /// Finishes this compilation and produces a serialized list of bytes.
143    ///
144    /// This method requires that either [`CodeBuilder::wasm`] or
145    /// [`CodeBuilder::wasm_file`] was invoked prior to indicate what is
146    /// being compiled.
147    ///
148    /// This method will block the current thread until compilation has
149    /// finished, and when done the serialized artifact will be returned.
150    ///
151    /// Note that this method will never cache compilations, even if the
152    /// `cache` feature is enabled.
153    ///
154    /// # Errors
155    ///
156    /// This can fail if the input wasm module was not valid or if another
157    /// compilation-related error is encountered.
158    pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
159        let wasm = self.wasm_binary()?;
160        let (v, _) = super::build_artifacts(self.engine, &wasm)?;
161        Ok(v)
162    }
163
164    /// Same as [`CodeBuilder::compile_module_serialized`] except that it
165    /// compiles a serialized [`Component`](crate::component::Component)
166    /// instead of a module.
167    #[cfg(feature = "component-model")]
168    #[cfg_attr(docsrs, doc(cfg(feature = "component-model")))]
169    pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
170        let bytes = self.wasm_binary()?;
171        let (v, _) = super::build_component_artifacts(self.engine, &bytes)?;
172        Ok(v)
173    }
174}
175
176/// This is a helper struct used when caching to hash the state of an `Engine`
177/// used for module compilation.
178///
179/// The hash computed for this structure is used to key the global wasmtime
180/// cache and dictates whether artifacts are reused. Consequently the contents
181/// of this hash dictate when artifacts are or aren't re-used.
182pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
183
184impl std::hash::Hash for HashedEngineCompileEnv<'_> {
185    fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
186        // Hash the compiler's state based on its target and configuration.
187        let compiler = self.0.compiler();
188        compiler.triple().hash(hasher);
189        compiler.flags().hash(hasher);
190        compiler.isa_flags().hash(hasher);
191
192        // Hash configuration state read for compilation
193        let config = self.0.config();
194        self.0.tunables().hash(hasher);
195        config.features.hash(hasher);
196        config.wmemcheck.hash(hasher);
197
198        // Catch accidental bugs of reusing across crate versions.
199        config.module_version.hash(hasher);
200    }
201}