wasmtime/compile/
code_builder.rs

1use crate::prelude::*;
2use crate::Engine;
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
22/// be provided with either the [`wasm_binary`] or [`wasm_binary_file`] method.
23/// Note that only a single source of bytes can be provided.
24///
25/// # WebAssembly Text Format
26///
27/// This builder supports the WebAssembly Text Format (`*.wat` files) through
28/// the [`CodeBuilder::wasm_binary_or_text`] and
29/// [`CodeBuilder::wasm_binary_or_text_file`] methods. These methods
30/// automatically convert WebAssembly text files to binary. Note though that
31/// this behavior is disabled if the `wat` crate feature is not enabled.
32///
33/// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
34/// [`compile_module`]: CodeBuilder::compile_module
35/// [`wasm_binary`]: CodeBuilder::wasm_binary
36/// [`wasm_binary_file`]: CodeBuilder::wasm_binary_file
37pub struct CodeBuilder<'a> {
38    pub(super) engine: &'a Engine,
39    wasm: Option<Cow<'a, [u8]>>,
40    wasm_path: Option<Cow<'a, Path>>,
41    dwarf_package: Option<Cow<'a, [u8]>>,
42    dwarf_package_path: Option<Cow<'a, Path>>,
43}
44
45/// Return value of [`CodeBuilder::hint`]
46pub enum CodeHint {
47    /// Hint that the code being compiled is a module.
48    Module,
49    /// Hint that the code being compiled is a component.
50    Component,
51}
52
53impl<'a> CodeBuilder<'a> {
54    /// Creates a new builder which will insert modules into the specified
55    /// [`Engine`].
56    pub fn new(engine: &'a Engine) -> CodeBuilder<'a> {
57        CodeBuilder {
58            engine,
59            wasm: None,
60            wasm_path: None,
61            dwarf_package: None,
62            dwarf_package_path: None,
63        }
64    }
65
66    /// Configures the WebAssembly binary that is being compiled.
67    ///
68    /// The `wasm_bytes` parameter must be a binary WebAssembly file.
69    /// This will be stored within the [`CodeBuilder`] for processing later when
70    /// compilation is finalized.
71    ///
72    /// The optional `wasm_path` parameter is the path to the `wasm_bytes` on
73    /// disk, if any. This may be used for diagnostics and other
74    /// debugging-related purposes, but this method will not read the path
75    /// specified.
76    ///
77    /// # Errors
78    ///
79    /// This method will return an error if WebAssembly bytes have already been
80    /// configured.
81    pub fn wasm_binary(
82        &mut self,
83        wasm_bytes: impl Into<Cow<'a, [u8]>>,
84        wasm_path: Option<&'a Path>,
85    ) -> Result<&mut Self> {
86        if self.wasm.is_some() {
87            bail!("cannot configure wasm bytes twice");
88        }
89        self.wasm = Some(wasm_bytes.into());
90        self.wasm_path = wasm_path.map(|p| p.into());
91
92        if self.wasm_path.is_some() {
93            self.dwarf_package_from_wasm_path()?;
94        }
95
96        Ok(self)
97    }
98
99    /// Equivalent of [`CodeBuilder::wasm_binary`] that also accepts the
100    /// WebAssembly text format.
101    ///
102    /// This method will configure the WebAssembly binary to be compiled. The
103    /// input `wasm_bytes` may either be the wasm text format or the binary
104    /// format. If the `wat` crate feature is enabled, which is enabled by
105    /// default, then the text format will automatically be converted to the
106    /// binary format.
107    ///
108    /// # Errors
109    ///
110    /// This method will return an error if WebAssembly bytes have already been
111    /// configured. This method will also return an error if `wasm_bytes` is the
112    /// wasm text format and the text syntax is not valid.
113    pub fn wasm_binary_or_text(
114        &mut self,
115        wasm_bytes: &'a [u8],
116        wasm_path: Option<&'a Path>,
117    ) -> Result<&mut Self> {
118        #[cfg(feature = "wat")]
119        let wasm_bytes = wat::parse_bytes(wasm_bytes).map_err(|mut e| {
120            if let Some(path) = wasm_path {
121                e.set_path(path);
122            }
123            e
124        })?;
125        self.wasm_binary(wasm_bytes, wasm_path)
126    }
127
128    /// Reads the `file` specified for the WebAssembly bytes that are going to
129    /// be compiled.
130    ///
131    /// This method will read `file` from the filesystem and interpret it
132    /// as a WebAssembly binary.
133    ///
134    /// A DWARF package file will be probed using the root of `file` and with a
135    /// `.dwp` extension. If found, it will be loaded and DWARF fusion
136    /// performed.
137    ///
138    /// # Errors
139    ///
140    /// This method will return an error if WebAssembly bytes have already been
141    /// configured.
142    ///
143    /// If `file` can't be read or an error happens reading it then that will
144    /// also be returned.
145    ///
146    /// If DWARF fusion is performed and the DWARF packaged file cannot be read
147    /// then an error will be returned.
148    pub fn wasm_binary_file(&mut self, file: &'a Path) -> Result<&mut Self> {
149        let wasm = std::fs::read(file)
150            .with_context(|| format!("failed to read input file: {}", file.display()))?;
151        self.wasm_binary(wasm, Some(file))
152    }
153
154    /// Equivalent of [`CodeBuilder::wasm_binary_file`] that also accepts the
155    /// WebAssembly text format.
156    ///
157    /// This method is will read the file at `path` and interpret the contents
158    /// to determine if it's the wasm text format or binary format. The file
159    /// extension of `file` is not consulted. The text format is automatically
160    /// converted to the binary format if the crate feature `wat` is active.
161    ///
162    /// # Errors
163    ///
164    /// In addition to the errors returned by [`CodeBuilder::wasm_binary_file`]
165    /// this may also fail if the text format is read and the syntax is invalid.
166    pub fn wasm_binary_or_text_file(&mut self, file: &'a Path) -> Result<&mut Self> {
167        #[cfg(feature = "wat")]
168        {
169            let wasm = wat::parse_file(file)?;
170            self.wasm_binary(wasm, Some(file))
171        }
172        #[cfg(not(feature = "wat"))]
173        {
174            self.wasm_binary_file(file)
175        }
176    }
177
178    pub(super) fn get_wasm(&self) -> Result<&[u8]> {
179        self.wasm
180            .as_deref()
181            .ok_or_else(|| anyhow!("no wasm bytes have been configured"))
182    }
183
184    /// Explicitly specify DWARF `.dwp` path.
185    ///
186    /// # Errors
187    ///
188    /// This method will return an error if the `.dwp` file has already been set
189    /// through [`CodeBuilder::dwarf_package`] or auto-detection in
190    /// [`CodeBuilder::wasm_binary_file`].
191    ///
192    /// This method will also return an error if `file` cannot be read.
193    pub fn dwarf_package_file(&mut self, file: &Path) -> Result<&mut Self> {
194        if self.dwarf_package.is_some() {
195            bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
196        }
197
198        let dwarf_package = std::fs::read(file)
199            .with_context(|| format!("failed to read dwarf input file: {}", file.display()))?;
200        self.dwarf_package_path = Some(Cow::Owned(file.to_owned()));
201        self.dwarf_package = Some(dwarf_package.into());
202
203        Ok(self)
204    }
205
206    fn dwarf_package_from_wasm_path(&mut self) -> Result<&mut Self> {
207        let dwarf_package_path_buf = self.wasm_path.as_ref().unwrap().with_extension("dwp");
208        if dwarf_package_path_buf.exists() {
209            return self.dwarf_package_file(dwarf_package_path_buf.as_path());
210        }
211
212        Ok(self)
213    }
214
215    /// Gets the DWARF package.
216    pub(super) fn get_dwarf_package(&self) -> Option<&[u8]> {
217        self.dwarf_package.as_deref()
218    }
219
220    /// Set the DWARF package binary.
221    ///
222    /// Initializes `dwarf_package` from `dwp_bytes` in preparation for
223    /// DWARF fusion. Allows the DWARF package to be supplied as a byte array
224    /// when the file probing performed in `wasm_file` is not appropriate.
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if the `*.dwp` file is already set via auto-probing in
229    /// [`CodeBuilder::wasm_binary_file`] or explicitly via
230    /// [`CodeBuilder::dwarf_package_file`].
231    pub fn dwarf_package(&mut self, dwp_bytes: &'a [u8]) -> Result<&mut Self> {
232        if self.dwarf_package.is_some() {
233            bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
234        }
235        self.dwarf_package = Some(dwp_bytes.into());
236        Ok(self)
237    }
238
239    /// Returns a hint, if possible, of what the provided bytes are.
240    ///
241    /// This method can be use to detect what the previously supplied bytes to
242    /// methods such as [`CodeBuilder::wasm_binary_or_text`] are. This will
243    /// return whether a module or a component was found in the provided bytes.
244    ///
245    /// This method will return `None` if wasm bytes have not been configured
246    /// or if the provided bytes don't look like either a component or a
247    /// module.
248    pub fn hint(&self) -> Option<CodeHint> {
249        let wasm = self.wasm.as_ref()?;
250        if wasmparser::Parser::is_component(wasm) {
251            Some(CodeHint::Component)
252        } else if wasmparser::Parser::is_core_wasm(wasm) {
253            Some(CodeHint::Module)
254        } else {
255            None
256        }
257    }
258
259    /// Finishes this compilation and produces a serialized list of bytes.
260    ///
261    /// This method requires that either [`CodeBuilder::wasm_binary`] or
262    /// related methods were invoked prior to indicate what is being compiled.
263    ///
264    /// This method will block the current thread until compilation has
265    /// finished, and when done the serialized artifact will be returned.
266    ///
267    /// Note that this method will never cache compilations, even if the
268    /// `cache` feature is enabled.
269    ///
270    /// # Errors
271    ///
272    /// This can fail if the input wasm module was not valid or if another
273    /// compilation-related error is encountered.
274    pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
275        let wasm = self.get_wasm()?;
276        let dwarf_package = self.get_dwarf_package();
277        let (v, _) = super::build_artifacts(self.engine, &wasm, dwarf_package.as_deref(), &())?;
278        Ok(v)
279    }
280
281    /// Same as [`CodeBuilder::compile_module_serialized`] except that it
282    /// compiles a serialized [`Component`](crate::component::Component)
283    /// instead of a module.
284    #[cfg(feature = "component-model")]
285    pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
286        let bytes = self.get_wasm()?;
287        let (v, _) = super::build_component_artifacts(self.engine, &bytes, None, &())?;
288        Ok(v)
289    }
290}
291
292/// This is a helper struct used when caching to hash the state of an `Engine`
293/// used for module compilation.
294///
295/// The hash computed for this structure is used to key the global wasmtime
296/// cache and dictates whether artifacts are reused. Consequently the contents
297/// of this hash dictate when artifacts are or aren't re-used.
298pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
299
300impl std::hash::Hash for HashedEngineCompileEnv<'_> {
301    fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
302        // Hash the compiler's state based on its target and configuration.
303        let compiler = self.0.compiler();
304        compiler.triple().hash(hasher);
305        compiler.flags().hash(hasher);
306        compiler.isa_flags().hash(hasher);
307
308        // Hash configuration state read for compilation
309        let config = self.0.config();
310        self.0.tunables().hash(hasher);
311        self.0.features().hash(hasher);
312        config.wmemcheck.hash(hasher);
313
314        // Catch accidental bugs of reusing across crate versions.
315        config.module_version.hash(hasher);
316    }
317}