1 use crate::Engine;
2 use crate::prelude::*;
3 use std::borrow::Cow;
4 use 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
37 pub 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`]
46 pub 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 
53 impl<'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.
298 pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
299 
300 impl 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 }
318