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