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