1 #[cfg(all(feature = "runtime", feature = "component-model"))]
2 use crate::component::Component;
3 use crate::Engine;
4 #[cfg(feature = "runtime")]
5 use crate::{instantiate::MmapVecWrapper, CodeMemory, Module};
6 use anyhow::{anyhow, bail, Context, Result};
7 use std::borrow::Cow;
8 use std::path::Path;
9 use std::sync::Arc;
10 use wasmtime_environ::ObjectKind;
11 #[cfg(feature = "runtime")]
12 use wasmtime_runtime::MmapVec;
13 
14 /// Builder-style structure used to create a [`Module`](crate::Module) or
15 /// pre-compile a module to a serialized list of bytes.
16 ///
17 /// This structure can be used for more advanced configuration when compiling a
18 /// WebAssembly module. Most configuration can use simpler constructors such as:
19 ///
20 /// * [`Module::new`](crate::Module::new)
21 /// * [`Module::from_file`](crate::Module::from_file)
22 /// * [`Module::from_binary`](crate::Module::from_binary)
23 ///
24 /// Note that a [`CodeBuilder`] always involves compiling WebAssembly bytes
25 /// to machine code. To deserialize a list of bytes use
26 /// [`Module::deserialize`](crate::Module::deserialize) instead.
27 ///
28 /// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured
29 /// before calling [`compile_module_serialized`] or [`compile_module`]. This can be
30 /// provided with either the [`wasm`] or [`wasm_file`] method. Note that only
31 /// a single source of bytes can be provided.
32 ///
33 /// # WebAssembly Text Format
34 ///
35 /// This builder supports the WebAssembly Text Format (`*.wat` files).
36 /// WebAssembly text files are automatically converted to a WebAssembly binary
37 /// and then the binary is compiled. This requires the `wat` feature of the
38 /// `wasmtime` crate to be enabled, and the feature is enabled by default.
39 ///
40 /// If the text format is not desired then the [`CodeBuilder::wat`] method
41 /// can be used to disable this conversion.
42 ///
43 /// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
44 /// [`compile_module`]: CodeBuilder::compile_module
45 /// [`wasm`]: CodeBuilder::wasm
46 /// [`wasm_file`]: CodeBuilder::wasm_file
47 pub struct CodeBuilder<'a> {
48     engine: &'a Engine,
49     wasm: Option<Cow<'a, [u8]>>,
50     wasm_path: Option<Cow<'a, Path>>,
51     wat: bool,
52 }
53 
54 impl<'a> CodeBuilder<'a> {
55     /// Creates a new builder which will insert modules into the specified
56     /// [`Engine`].
57     pub fn new(engine: &'a Engine) -> CodeBuilder<'a> {
58         CodeBuilder {
59             engine,
60             wasm: None,
61             wasm_path: None,
62             wat: cfg!(feature = "wat"),
63         }
64     }
65 
66     /// Configures the WebAssembly binary or text that is being compiled.
67     ///
68     /// The `wasm_bytes` parameter is either a binary WebAssembly file or a
69     /// WebAssembly module in its text format. This will be stored within the
70     /// [`CodeBuilder`] for processing later when 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     /// If wasm bytes have already been configured via a call to this method or
80     /// [`CodeBuilder::wasm_file`] then an error will be returned.
81     pub fn wasm(&mut self, wasm_bytes: &'a [u8], wasm_path: Option<&'a Path>) -> Result<&mut Self> {
82         if self.wasm.is_some() {
83             bail!("cannot call `wasm` or `wasm_file` twice");
84         }
85         self.wasm = Some(wasm_bytes.into());
86         self.wasm_path = wasm_path.map(|p| p.into());
87         Ok(self)
88     }
89 
90     /// Configures whether the WebAssembly text format is supported in this
91     /// builder.
92     ///
93     /// This support is enabled by default if the `wat` crate feature is also
94     /// enabled.
95     ///
96     /// # Errors
97     ///
98     /// If this feature is explicitly enabled here via this method and the
99     /// `wat` crate feature is disabled then an error will be returned.
100     pub fn wat(&mut self, enable: bool) -> Result<&mut Self> {
101         if !cfg!(feature = "wat") && enable {
102             bail!("support for `wat` was disabled at compile time");
103         }
104         self.wat = enable;
105         Ok(self)
106     }
107 
108     /// Reads the `file` specified for the WebAssembly bytes that are going to
109     /// be compiled.
110     ///
111     /// This method will read `file` from the filesystem and interpret it
112     /// either as a WebAssembly binary or as a WebAssembly text file. The
113     /// contents are inspected to do this, the file extension is not consulted.
114     ///
115     /// # Errors
116     ///
117     /// If wasm bytes have already been configured via a call to this method or
118     /// [`CodeBuilder::wasm`] then an error will be returned.
119     ///
120     /// If `file` can't be read or an error happens reading it then that will
121     /// also be returned.
122     pub fn wasm_file(&mut self, file: &'a Path) -> Result<&mut Self> {
123         if self.wasm.is_some() {
124             bail!("cannot call `wasm` or `wasm_file` twice");
125         }
126         let wasm = std::fs::read(file)
127             .with_context(|| format!("failed to read input file: {}", file.display()))?;
128         self.wasm = Some(wasm.into());
129         self.wasm_path = Some(file.into());
130         Ok(self)
131     }
132 
133     fn wasm_binary(&self) -> Result<Cow<'_, [u8]>> {
134         let wasm = self
135             .wasm
136             .as_ref()
137             .ok_or_else(|| anyhow!("no wasm bytes have been configured"))?;
138         if self.wat {
139             #[cfg(feature = "wat")]
140             return wat::parse_bytes(wasm).map_err(|mut e| {
141                 if let Some(path) = &self.wasm_path {
142                     e.set_path(path);
143                 }
144                 e.into()
145             });
146         }
147         Ok((&wasm[..]).into())
148     }
149 
150     #[cfg(feature = "runtime")]
151     fn compile_cached<T>(
152         &self,
153         build_artifacts: fn(&Engine, &[u8]) -> Result<(MmapVecWrapper, Option<T>)>,
154     ) -> Result<(Arc<CodeMemory>, Option<T>)> {
155         let wasm = self.wasm_binary()?;
156 
157         self.engine
158             .check_compatible_with_native_host()
159             .context("compilation settings are not compatible with the native host")?;
160 
161         #[cfg(feature = "cache")]
162         {
163             let state = (
164                 HashedEngineCompileEnv(self.engine),
165                 &wasm,
166                 // Don't hash this as it's just its own "pure" function pointer.
167                 NotHashed(build_artifacts),
168             );
169             let (code, info_and_types) =
170                 wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache_config())
171                     .get_data_raw(
172                         &state,
173                         // Cache miss, compute the actual artifacts
174                         |(engine, wasm, build_artifacts)| -> Result<_> {
175                             let (mmap, info) = (build_artifacts.0)(engine.0, wasm)?;
176                             let code = publish_mmap(mmap.0)?;
177                             Ok((code, info))
178                         },
179                         // Implementation of how to serialize artifacts
180                         |(_engine, _wasm, _), (code, _info_and_types)| Some(code.mmap().to_vec()),
181                         // Cache hit, deserialize the provided artifacts
182                         |(engine, wasm, _), serialized_bytes| {
183                             let kind = if wasmparser::Parser::is_component(&wasm) {
184                                 ObjectKind::Component
185                             } else {
186                                 ObjectKind::Module
187                             };
188                             let code = engine.0.load_code_bytes(&serialized_bytes, kind).ok()?;
189                             Some((code, None))
190                         },
191                     )?;
192             return Ok((code, info_and_types));
193         }
194 
195         #[cfg(not(feature = "cache"))]
196         {
197             let (mmap, info_and_types) = build_artifacts(self.engine, &wasm)?;
198             let code = publish_mmap(mmap.0)?;
199             return Ok((code, info_and_types));
200         }
201 
202         struct NotHashed<T>(T);
203 
204         impl<T> std::hash::Hash for NotHashed<T> {
205             fn hash<H: std::hash::Hasher>(&self, _hasher: &mut H) {}
206         }
207     }
208 
209     /// Finishes this compilation and produces a serialized list of bytes.
210     ///
211     /// This method requires that either [`CodeBuilder::wasm`] or
212     /// [`CodeBuilder::wasm_file`] was invoked prior to indicate what is
213     /// being compiled.
214     ///
215     /// This method will block the current thread until compilation has
216     /// finished, and when done the serialized artifact will be returned.
217     ///
218     /// Note that this method will never cache compilations, even if the
219     /// `cache` feature is enabled.
220     ///
221     /// # Errors
222     ///
223     /// This can fail if the input wasm module was not valid or if another
224     /// compilation-related error is encountered.
225     pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
226         let wasm = self.wasm_binary()?;
227         let (v, _) = super::build_artifacts(self.engine, &wasm)?;
228         Ok(v)
229     }
230 
231     /// Same as [`CodeBuilder::compile_module_serialized`] except that a
232     /// [`Module`](crate::Module) is produced instead.
233     ///
234     /// Note that this method will cache compilations if the `cache` feature is
235     /// enabled and turned on in [`Config`](crate::Config).
236     #[cfg(feature = "runtime")]
237     #[cfg_attr(docsrs, doc(cfg(feature = "runtime")))]
238     pub fn compile_module(&self) -> Result<Module> {
239         let (code, info_and_types) = self.compile_cached(super::build_artifacts)?;
240         Module::from_parts(self.engine, code, info_and_types)
241     }
242 
243     /// Same as [`CodeBuilder::compile_module_serialized`] except that it
244     /// compiles a serialized [`Component`] instead of a module.
245     #[cfg(feature = "component-model")]
246     #[cfg_attr(docsrs, doc(cfg(feature = "component-model")))]
247     pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
248         let bytes = self.wasm_binary()?;
249         let (v, _) = super::build_component_artifacts(self.engine, &bytes)?;
250         Ok(v)
251     }
252 
253     /// Same as [`CodeBuilder::compile_module`] except that it compiles a
254     /// [`Component`] instead of a module.
255     #[cfg(all(feature = "runtime", feature = "component-model"))]
256     #[cfg_attr(
257         docsrs,
258         doc(cfg(all(feature = "runtime", feature = "component-model")))
259     )]
260     pub fn compile_component(&self) -> Result<Component> {
261         let (code, artifacts) = self.compile_cached(super::build_component_artifacts)?;
262         Component::from_parts(self.engine, code, artifacts)
263     }
264 }
265 
266 /// This is a helper struct used when caching to hash the state of an `Engine`
267 /// used for module compilation.
268 ///
269 /// The hash computed for this structure is used to key the global wasmtime
270 /// cache and dictates whether artifacts are reused. Consequently the contents
271 /// of this hash dictate when artifacts are or aren't re-used.
272 pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
273 
274 impl std::hash::Hash for HashedEngineCompileEnv<'_> {
275     fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
276         // Hash the compiler's state based on its target and configuration.
277         let compiler = self.0.compiler();
278         compiler.triple().hash(hasher);
279         compiler.flags().hash(hasher);
280         compiler.isa_flags().hash(hasher);
281 
282         // Hash configuration state read for compilation
283         let config = self.0.config();
284         self.0.tunables().hash(hasher);
285         config.features.hash(hasher);
286         config.wmemcheck.hash(hasher);
287 
288         // Catch accidental bugs of reusing across crate versions.
289         config.module_version.hash(hasher);
290     }
291 }
292 
293 #[cfg(feature = "runtime")]
294 fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
295     let mut code = CodeMemory::new(mmap)?;
296     code.publish()?;
297     Ok(Arc::new(code))
298 }
299