1 use crate::prelude::*; 2 use crate::Engine; 3 use std::borrow::Cow; 4 use std::path::Path; 5 6 /// Builder-style structure used to create a [`Module`](crate::module::Module) or 7 /// pre-compile a module to a serialized list of bytes. 8 /// 9 /// This structure can be used for more advanced configuration when compiling a 10 /// WebAssembly module. Most configuration can use simpler constructors such as: 11 /// 12 /// * [`Module::new`](crate::Module::new) 13 /// * [`Module::from_file`](crate::Module::from_file) 14 /// * [`Module::from_binary`](crate::Module::from_binary) 15 /// 16 /// Note that a [`CodeBuilder`] always involves compiling WebAssembly bytes 17 /// to machine code. To deserialize a list of bytes use 18 /// [`Module::deserialize`](crate::Module::deserialize) instead. 19 /// 20 /// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured 21 /// before calling [`compile_module_serialized`] or [`compile_module`]. This can 22 /// be provided with either the [`wasm_binary`] or [`wasm_binary_file`] method. 23 /// Note that only a single source of bytes can be provided. 24 /// 25 /// # WebAssembly Text Format 26 /// 27 /// This builder supports the WebAssembly Text Format (`*.wat` files) through 28 /// the [`CodeBuilder::wasm_binary_or_text`] and 29 /// [`CodeBuilder::wasm_binary_or_text_file`] methods. These methods 30 /// automatically convert WebAssembly text files to binary. Note though that 31 /// this behavior is disabled if the `wat` crate feature is not enabled. 32 /// 33 /// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized 34 /// [`compile_module`]: CodeBuilder::compile_module 35 /// [`wasm_binary`]: CodeBuilder::wasm_binary 36 /// [`wasm_binary_file`]: CodeBuilder::wasm_binary_file 37 pub struct CodeBuilder<'a> { 38 pub(super) engine: &'a Engine, 39 wasm: Option<Cow<'a, [u8]>>, 40 wasm_path: Option<Cow<'a, Path>>, 41 dwarf_package: Option<Cow<'a, [u8]>>, 42 dwarf_package_path: Option<Cow<'a, Path>>, 43 } 44 45 /// Return value of [`CodeBuilder::hint`] 46 pub enum CodeHint { 47 /// Hint that the code being compiled is a module. 48 Module, 49 /// Hint that the code being compiled is a component. 50 Component, 51 } 52 53 impl<'a> CodeBuilder<'a> { 54 /// Creates a new builder which will insert modules into the specified 55 /// [`Engine`]. 56 pub fn new(engine: &'a Engine) -> CodeBuilder<'a> { 57 CodeBuilder { 58 engine, 59 wasm: None, 60 wasm_path: None, 61 dwarf_package: None, 62 dwarf_package_path: None, 63 } 64 } 65 66 /// Configures the WebAssembly binary that is being compiled. 67 /// 68 /// The `wasm_bytes` parameter must be a binary WebAssembly file. 69 /// This will be stored within the [`CodeBuilder`] for processing later when 70 /// compilation is finalized. 71 /// 72 /// The optional `wasm_path` parameter is the path to the `wasm_bytes` on 73 /// disk, if any. This may be used for diagnostics and other 74 /// debugging-related purposes, but this method will not read the path 75 /// specified. 76 /// 77 /// # Errors 78 /// 79 /// This method will return an error if WebAssembly bytes have already been 80 /// configured. 81 pub fn wasm_binary( 82 &mut self, 83 wasm_bytes: impl Into<Cow<'a, [u8]>>, 84 wasm_path: Option<&'a Path>, 85 ) -> Result<&mut Self> { 86 if self.wasm.is_some() { 87 bail!("cannot configure wasm bytes twice"); 88 } 89 self.wasm = Some(wasm_bytes.into()); 90 self.wasm_path = wasm_path.map(|p| p.into()); 91 92 if self.wasm_path.is_some() { 93 self.dwarf_package_from_wasm_path()?; 94 } 95 96 Ok(self) 97 } 98 99 /// Equivalent of [`CodeBuilder::wasm_binary`] that also accepts the 100 /// WebAssembly text format. 101 /// 102 /// This method will configure the WebAssembly binary to be compiled. The 103 /// input `wasm_bytes` may either be the wasm text format or the binary 104 /// format. If the `wat` crate feature is enabled, which is enabled by 105 /// default, then the text format will automatically be converted to the 106 /// binary format. 107 /// 108 /// # Errors 109 /// 110 /// This method will return an error if WebAssembly bytes have already been 111 /// configured. This method will also return an error if `wasm_bytes` is the 112 /// wasm text format and the text syntax is not valid. 113 pub fn wasm_binary_or_text( 114 &mut self, 115 wasm_bytes: &'a [u8], 116 wasm_path: Option<&'a Path>, 117 ) -> Result<&mut Self> { 118 #[cfg(feature = "wat")] 119 let wasm_bytes = wat::parse_bytes(wasm_bytes).map_err(|mut e| { 120 if let Some(path) = wasm_path { 121 e.set_path(path); 122 } 123 e 124 })?; 125 self.wasm_binary(wasm_bytes, wasm_path) 126 } 127 128 /// Reads the `file` specified for the WebAssembly bytes that are going to 129 /// be compiled. 130 /// 131 /// This method will read `file` from the filesystem and interpret it 132 /// as a WebAssembly binary. 133 /// 134 /// A DWARF package file will be probed using the root of `file` and with a 135 /// `.dwp` extension. If found, it will be loaded and DWARF fusion 136 /// performed. 137 /// 138 /// # Errors 139 /// 140 /// This method will return an error if WebAssembly bytes have already been 141 /// configured. 142 /// 143 /// If `file` can't be read or an error happens reading it then that will 144 /// also be returned. 145 /// 146 /// If DWARF fusion is performed and the DWARF packaged file cannot be read 147 /// then an error will be returned. 148 pub fn wasm_binary_file(&mut self, file: &'a Path) -> Result<&mut Self> { 149 let wasm = std::fs::read(file) 150 .with_context(|| format!("failed to read input file: {}", file.display()))?; 151 self.wasm_binary(wasm, Some(file)) 152 } 153 154 /// Equivalent of [`CodeBuilder::wasm_binary_file`] that also accepts the 155 /// WebAssembly text format. 156 /// 157 /// This method is will read the file at `path` and interpret the contents 158 /// to determine if it's the wasm text format or binary format. The file 159 /// extension of `file` is not consulted. The text format is automatically 160 /// converted to the binary format if the crate feature `wat` is active. 161 /// 162 /// # Errors 163 /// 164 /// In addition to the errors returned by [`CodeBuilder::wasm_binary_file`] 165 /// this may also fail if the text format is read and the syntax is invalid. 166 pub fn wasm_binary_or_text_file(&mut self, file: &'a Path) -> Result<&mut Self> { 167 #[cfg(feature = "wat")] 168 { 169 let wasm = wat::parse_file(file)?; 170 self.wasm_binary(wasm, Some(file)) 171 } 172 #[cfg(not(feature = "wat"))] 173 { 174 self.wasm_binary_file(file) 175 } 176 } 177 178 pub(super) fn get_wasm(&self) -> Result<&[u8]> { 179 self.wasm 180 .as_deref() 181 .ok_or_else(|| anyhow!("no wasm bytes have been configured")) 182 } 183 184 /// Explicitly specify DWARF `.dwp` path. 185 /// 186 /// # Errors 187 /// 188 /// This method will return an error if the `.dwp` file has already been set 189 /// through [`CodeBuilder::dwarf_package`] or auto-detection in 190 /// [`CodeBuilder::wasm_binary_file`]. 191 /// 192 /// This method will also return an error if `file` cannot be read. 193 pub fn dwarf_package_file(&mut self, file: &Path) -> Result<&mut Self> { 194 if self.dwarf_package.is_some() { 195 bail!("cannot call `dwarf_package` or `dwarf_package_file` twice"); 196 } 197 198 let dwarf_package = std::fs::read(file) 199 .with_context(|| format!("failed to read dwarf input file: {}", file.display()))?; 200 self.dwarf_package_path = Some(Cow::Owned(file.to_owned())); 201 self.dwarf_package = Some(dwarf_package.into()); 202 203 Ok(self) 204 } 205 206 fn dwarf_package_from_wasm_path(&mut self) -> Result<&mut Self> { 207 let dwarf_package_path_buf = self.wasm_path.as_ref().unwrap().with_extension("dwp"); 208 if dwarf_package_path_buf.exists() { 209 return self.dwarf_package_file(dwarf_package_path_buf.as_path()); 210 } 211 212 Ok(self) 213 } 214 215 /// Gets the DWARF package. 216 pub(super) fn get_dwarf_package(&self) -> Option<&[u8]> { 217 self.dwarf_package.as_deref() 218 } 219 220 /// Set the DWARF package binary. 221 /// 222 /// Initializes `dwarf_package` from `dwp_bytes` in preparation for 223 /// DWARF fusion. Allows the DWARF package to be supplied as a byte array 224 /// when the file probing performed in `wasm_file` is not appropriate. 225 /// 226 /// # Errors 227 /// 228 /// Returns an error if the `*.dwp` file is already set via auto-probing in 229 /// [`CodeBuilder::wasm_binary_file`] or explicitly via 230 /// [`CodeBuilder::dwarf_package_file`]. 231 pub fn dwarf_package(&mut self, dwp_bytes: &'a [u8]) -> Result<&mut Self> { 232 if self.dwarf_package.is_some() { 233 bail!("cannot call `dwarf_package` or `dwarf_package_file` twice"); 234 } 235 self.dwarf_package = Some(dwp_bytes.into()); 236 Ok(self) 237 } 238 239 /// Returns a hint, if possible, of what the provided bytes are. 240 /// 241 /// This method can be use to detect what the previously supplied bytes to 242 /// methods such as [`CodeBuilder::wasm_binary_or_text`] are. This will 243 /// return whether a module or a component was found in the provided bytes. 244 /// 245 /// This method will return `None` if wasm bytes have not been configured 246 /// or if the provided bytes don't look like either a component or a 247 /// module. 248 pub fn hint(&self) -> Option<CodeHint> { 249 let wasm = self.wasm.as_ref()?; 250 if wasmparser::Parser::is_component(wasm) { 251 Some(CodeHint::Component) 252 } else if wasmparser::Parser::is_core_wasm(wasm) { 253 Some(CodeHint::Module) 254 } else { 255 None 256 } 257 } 258 259 /// Finishes this compilation and produces a serialized list of bytes. 260 /// 261 /// This method requires that either [`CodeBuilder::wasm_binary`] or 262 /// related methods were invoked prior to indicate what is being compiled. 263 /// 264 /// This method will block the current thread until compilation has 265 /// finished, and when done the serialized artifact will be returned. 266 /// 267 /// Note that this method will never cache compilations, even if the 268 /// `cache` feature is enabled. 269 /// 270 /// # Errors 271 /// 272 /// This can fail if the input wasm module was not valid or if another 273 /// compilation-related error is encountered. 274 pub fn compile_module_serialized(&self) -> Result<Vec<u8>> { 275 let wasm = self.get_wasm()?; 276 let dwarf_package = self.get_dwarf_package(); 277 let (v, _) = super::build_artifacts(self.engine, &wasm, dwarf_package.as_deref(), &())?; 278 Ok(v) 279 } 280 281 /// Same as [`CodeBuilder::compile_module_serialized`] except that it 282 /// compiles a serialized [`Component`](crate::component::Component) 283 /// instead of a module. 284 #[cfg(feature = "component-model")] 285 pub fn compile_component_serialized(&self) -> Result<Vec<u8>> { 286 let bytes = self.get_wasm()?; 287 let (v, _) = super::build_component_artifacts(self.engine, &bytes, None, &())?; 288 Ok(v) 289 } 290 } 291 292 /// This is a helper struct used when caching to hash the state of an `Engine` 293 /// used for module compilation. 294 /// 295 /// The hash computed for this structure is used to key the global wasmtime 296 /// cache and dictates whether artifacts are reused. Consequently the contents 297 /// of this hash dictate when artifacts are or aren't re-used. 298 pub struct HashedEngineCompileEnv<'a>(pub &'a Engine); 299 300 impl std::hash::Hash for HashedEngineCompileEnv<'_> { 301 fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) { 302 // Hash the compiler's state based on its target and configuration. 303 let compiler = self.0.compiler(); 304 compiler.triple().hash(hasher); 305 compiler.flags().hash(hasher); 306 compiler.isa_flags().hash(hasher); 307 308 // Hash configuration state read for compilation 309 let config = self.0.config(); 310 self.0.tunables().hash(hasher); 311 self.0.features().hash(hasher); 312 config.wmemcheck.hash(hasher); 313 314 // Catch accidental bugs of reusing across crate versions. 315 config.module_version.hash(hasher); 316 } 317 } 318