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