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