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