1 use super::*;
2 
3 impl<'a> CodeBuilder<'a> {
4     pub(crate) fn get_compile_time_builtins(&self) -> &HashMap<Cow<'a, str>, Cow<'a, [u8]>> {
5         &self.compile_time_builtins
6     }
7 
8     pub(super) fn compose_compile_time_builtins<'b>(
9         &self,
10         main_wasm: &'b [u8],
11     ) -> Result<Cow<'b, [u8]>> {
12         if self.get_compile_time_builtins().is_empty() {
13             return Ok(main_wasm.into());
14         }
15 
16         let imports = self.check_imports_for_compile_time_builtins(&main_wasm)?;
17         if imports.is_empty() {
18             drop(imports);
19             return Ok(main_wasm.into());
20         }
21 
22         let tempdir = tempfile::TempDir::new().context("failed to create a temporary directory")?;
23         let deps = tempdir.path().join("_deps");
24         std::fs::create_dir(&deps)
25             .with_context(|| format!("failed to create directory: {}", deps.display()))?;
26 
27         let main_wasm_path = tempdir.path().join("_main.wasm");
28         std::fs::write(&main_wasm_path, &main_wasm)
29             .with_context(|| format!("failed to write to file: {}", main_wasm_path.display()))?;
30 
31         let mut config = wasm_compose::config::Config::default();
32         for (name, bytes) in self.get_compile_time_builtins() {
33             let name: &str = &*name;
34             if !imports.contains(&name) {
35                 continue;
36             }
37 
38             let mut path = deps.join(Path::new(name));
39             path.set_extension("wasm");
40 
41             std::fs::write(&path, &bytes)
42                 .with_context(|| format!("failed to write to file: {}", path.display()))?;
43 
44             config
45                 .dependencies
46                 .insert(name.to_string(), wasm_compose::config::Dependency { path });
47         }
48 
49         let composer = wasm_compose::composer::ComponentComposer::new(&main_wasm_path, &config);
50         let composed = composer.compose()?;
51         Ok(composed.into())
52     }
53 
54     /// Check that the main Wasm doesn't import unsafe intrinsics, keeping the
55     /// TCB to just the compile-time builtins' implementation.
56     ///
57     /// Returns the Wasm's top-level instance imports for `wasm-compose`
58     /// configuration.
59     fn check_imports_for_compile_time_builtins<'b>(
60         &self,
61         main_wasm: &'b [u8],
62     ) -> Result<crate::hash_set::HashSet<&'b str>, Error> {
63         let intrinsics_import = self.unsafe_intrinsics_import.as_deref().ok_or_else(|| {
64             format_err!(
65                 "must configure the unsafe-intrinsics import when using compile-time builtins"
66             )
67         })?;
68 
69         let mut instance_imports = crate::hash_set::HashSet::new();
70         let parser = wasmparser::Parser::new(0);
71         let mut level = 0;
72 
73         for payload in parser.parse_all(main_wasm) {
74             match payload? {
75                 wasmparser::Payload::Version { .. } => {
76                     level += 1;
77                 }
78                 wasmparser::Payload::End(_) => {
79                     level -= 1;
80                 }
81                 wasmparser::Payload::ComponentImportSection(imports) if level == 1 => {
82                     for imp in imports.into_iter() {
83                         let imp = imp?;
84                         // Ideally we would simply choose a new import name that
85                         // doesn't conflict with the main Wasm's imports and
86                         // plumb that through to the compile-time builtins
87                         // regardless of the import name that they use, but
88                         // unfortunately the `wasm-compose` API is not powerful
89                         // enough for us to do all that.
90                         ensure!(
91                             imp.name.0 != intrinsics_import,
92                             "main Wasm cannot import the unsafe intrinsics (`{intrinsics_import}`) \
93                              when using compile-time builtins"
94                         );
95 
96                         if let wasmparser::ComponentTypeRef::Instance(_) = imp.ty {
97                             instance_imports.insert(imp.name.0);
98                         }
99                     }
100                 }
101                 _ => {}
102             }
103         }
104 
105         Ok(instance_imports)
106     }
107 
108     /// Define a compile-time builtin component, via its Wasm bytes.
109     ///
110     /// Compile-time builtins enable you to build safe, zero-copy, and (with
111     /// [inlining][crate::Config::compiler_inlining])
112     /// zero-function-call-overhead Wasm APIs for accessing host data, buffers,
113     /// and objects.
114     ///
115     /// A compile-time builtin is a component that is
116     ///
117     /// * authored by the host (Wasmtime embedder),
118     ///
119     /// * whose implementation (though not necessarily its interface!) is
120     ///   host-specific,
121     ///
122     /// * has access to unsafe intrinsics (and is therefore part of the host's
123     ///   [trusted compute base]), and
124     ///
125     /// * is linked into guest Wasm programs at compile-time.
126     ///
127     /// Any imports satisfied by a compile-time builtin during compilation will
128     /// not show up in the resulting component's
129     /// [imports][crate::component::types::Component::imports], and they can no
130     /// longer be customized by a [`Linker`][crate::component::Linker]
131     /// definition at instantiation time.[^0]
132     ///
133     /// [^0]: If linking compile-time builtins into a component at compile-time
134     /// reminds you of [component composition], that is not a coincidence:
135     /// component composition is used under the covers as part of compile-time
136     /// builtins' implementation.
137     ///
138     /// Comparing compile-time builtins with
139     /// [`Linker`][crate::component::Linker]s is informative:
140     ///
141     /// * Both mechanisms define APIs to satisfy a Wasm program's imports.
142     ///
143     /// * A `Linker` satisfies those imports at instantiation-time, while
144     ///   compile-time builtins do it during compilation.
145     ///
146     /// * APIs defined by a `Linker` are implemented in Rust, and hosts can
147     ///   build safe, sandboxed Wasm APIs on top of raw, un-sandboxed primitives
148     ///   via Rust's `unsafe`. APIs defined by compile-time builtins are
149     ///   implemented as Wasm components, and hosts can build safe, sandboxed
150     ///   Wasm APIs on top of raw, un-sandboxed primitives via [unsafe
151     ///   intrinsics][CodeBuilder::expose_unsafe_intrinsics].
152     ///
153     /// * Imports satisfied via `Linker`-defined APIs are implemented with
154     ///   [PLT/GOT]-style function table lookups and indirect calls in the
155     ///   Wasm's compiled native code. On the other hand, Wasmtime implements
156     ///   calls to imports satisfied via compile-time builtins with direct calls
157     ///   in the Wasm's compiled native code. Wasmtime's compiler can also
158     ///   [inline][crate::Config::compiler_inlining] these direct calls,
159     ///   removing function call overheads and enabling further, cascading
160     ///   compiler optimizations.
161     ///
162     /// If you are familiar with Wasm on the Web, you can think of compile-time
163     /// builtins as the rough equivalent of [the `js-string-builtins` proposal]
164     /// but for arbitrary host-defined APIs in a Wasmtime embedding environment
165     /// rather than JS string APIs in a Web browser environment.
166     ///
167     /// [trusted compute base]: https://en.wikipedia.org/wiki/Trusted_computing_base
168     /// [the `js-string-builtins` proposal]: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md
169     /// [component composition]: https://component-model.bytecodealliance.org/composing-and-distributing/composing.html
170     /// [PLT/GOT]: https://reverseengineering.stackexchange.com/a/1993
171     ///
172     /// # Safety
173     ///
174     /// Compile-time builtins are part of your [trusted compute base] and should
175     /// be authored by trusted, first-party developers with extreme care. You
176     /// should never use compile-time builtins authored by untrusted,
177     /// third-party developers.
178     ///
179     /// Compile-time builtins are given access to Wasmtime's [unsafe
180     /// intrinsics][CodeBuilder::expose_unsafe_intrinsics], and the same safety
181     /// invariants and portability concerns apply. However, when compile-time
182     /// builtins are defined on a `CodeBuilder`, unsafe intrinsics are *only*
183     /// exposed to the compile-time builtins, and they are *not* exposed to the
184     /// main guest Wasm program. This means that — assuming your compile-time
185     /// builtins only exposing safe APIs, encapsulating the intrinsics'
186     /// unsafety, and modulo bugs in your implementation of those safe APIs —
187     /// that the main guest Wasm program is not part of your trusted compute
188     /// base.
189     ///
190     /// # Example
191     ///
192     /// See the example in [CodeBuilder::expose_unsafe_intrinsics].
193     pub unsafe fn compile_time_builtins_binary(
194         &mut self,
195         name: impl Into<Cow<'a, str>>,
196         wasm_bytes: impl Into<Cow<'a, [u8]>>,
197     ) -> &mut Self {
198         self.compile_time_builtins
199             .insert(name.into(), wasm_bytes.into());
200         self
201     }
202 
203     /// Equivalent of [`CodeBuilder::compile_time_builtins_binary`] that also
204     /// accepts the WebAssembly text format.
205     ///
206     /// This method will configure the WebAssembly binary to be compiled and
207     /// used to satisfy the `name` instance import. The input `wasm_bytes` may
208     /// either be the wasm text format or the binary format. If the `wat` crate
209     /// feature is enabled, which is enabled by default, then the text format
210     /// will automatically be converted to the binary format.
211     ///
212     /// # Errors
213     ///
214     /// This method will also return an error if `wasm_bytes` is the wasm text
215     /// format and the text syntax is not valid.
216     ///
217     /// # Safety
218     ///
219     /// See [`CodeBuilder::compile_time_builtins_binary`].
220     ///
221     /// # Example
222     ///
223     /// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
224     /// compile-time builtins.
225     pub unsafe fn compile_time_builtins_binary_or_text(
226         &mut self,
227         name: impl Into<Cow<'a, str>>,
228         wasm_bytes: impl Into<Cow<'a, [u8]>>,
229         wasm_path: Option<&Path>,
230     ) -> Result<&mut Self> {
231         let wasm_bytes = wasm_bytes.into();
232 
233         #[cfg(feature = "wat")]
234         if let Cow::Owned(wasm_bytes) = wat::parse_bytes(&wasm_bytes).map_err(|mut e| {
235             if let Some(path) = wasm_path {
236                 e.set_path(path);
237             }
238             e
239         })? {
240             // SAFETY: Same as our unsafe contract.
241             return Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) });
242         }
243 
244         // SAFETY: Same as our unsafe contract.
245         Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) })
246     }
247 
248     /// Like [`CodeBuilder::compile_time_builtins_binary`], but reads the `file`
249     /// specified for the bytes that will define the compile-time builtin.
250     ///
251     /// # Safety
252     ///
253     /// See [`CodeBuilder::compile_time_builtins_binary`].
254     ///
255     /// # Example
256     ///
257     /// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
258     /// compile-time builtins.
259     pub unsafe fn compile_time_builtins_binary_file(
260         &mut self,
261         name: impl Into<Cow<'a, str>>,
262         file: &Path,
263     ) -> Result<&mut Self> {
264         let wasm_bytes = std::fs::read(file)
265             .with_context(|| format!("failed to read file: {}", file.display()))?;
266         // SAFETY: Same as our unsafe contract.
267         Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) })
268     }
269 
270     /// Equivalent of [`CodeBuilder::compile_time_builtins_binary_file`] that
271     /// also accepts the WebAssembly text format.
272     ///
273     /// This method is will read the file at the given path and interpret the
274     /// contents to determine if it's the Wasm text format or binary format. The
275     /// file extension is not consulted. The text format is automatically
276     /// converted to the binary format if the crate feature `wat` is active.
277     ///
278     /// # Errors
279     ///
280     /// In addition to the errors returned by
281     /// [`CodeBuilder::compile_time_builtins_binary_file`] this may also fail if
282     /// the text format is read and the syntax is invalid.
283     ///
284     /// # Safety
285     ///
286     /// See [`CodeBuilder::compile_time_builtins_binary`].
287     ///
288     /// # Example
289     ///
290     /// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
291     /// compile-time builtins.
292     pub unsafe fn compile_time_builtins_binary_or_text_file(
293         &mut self,
294         name: impl Into<Cow<'a, str>>,
295         file: &Path,
296     ) -> Result<&mut Self> {
297         #[cfg(feature = "wat")]
298         {
299             let wasm = wat::parse_file(file)
300                 .with_context(|| format!("error parsing file: {}", file.display()))?;
301             // SAFETY: Same as our unsafe contract.
302             Ok(unsafe { self.compile_time_builtins_binary(name, wasm) })
303         }
304 
305         #[cfg(not(feature = "wat"))]
306         {
307             // SAFETY: Same as our unsafe contract.
308             unsafe { self.compile_time_builtins_binary_file(name, file) }
309         }
310     }
311 }
312