1 //! The module that implements the `wasmtime compile` command. 2 3 use anyhow::{bail, Context, Result}; 4 use clap::Parser; 5 use once_cell::sync::Lazy; 6 use std::fs; 7 use std::path::PathBuf; 8 use target_lexicon::Triple; 9 use wasmtime::Engine; 10 use wasmtime_cli_flags::CommonOptions; 11 12 static AFTER_HELP: Lazy<String> = Lazy::new(|| { 13 format!( 14 "By default, no CPU features or presets will be enabled for the compilation.\n\ 15 \n\ 16 {}\ 17 \n\ 18 Usage examples:\n\ 19 \n\ 20 Compiling a WebAssembly module for the current platform:\n\ 21 \n \ 22 wasmtime compile example.wasm 23 \n\ 24 Specifying the output file:\n\ 25 \n \ 26 wasmtime compile -o output.cwasm input.wasm\n\ 27 \n\ 28 Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\ 29 \n \ 30 wasmtime compile --target x86_64-unknown-linux --cranelift-enable skylake foo.wasm\n", 31 crate::FLAG_EXPLANATIONS.as_str() 32 ) 33 }); 34 35 /// Compiles a WebAssembly module. 36 #[derive(Parser)] 37 #[structopt( 38 name = "compile", 39 version, 40 after_help = AFTER_HELP.as_str() 41 )] 42 pub struct CompileCommand { 43 #[clap(flatten)] 44 common: CommonOptions, 45 46 /// The target triple; default is the host triple 47 #[clap(long, value_name = "TARGET")] 48 target: Option<String>, 49 50 /// The path of the output compiled module; defaults to <MODULE>.cwasm 51 #[clap(short = 'o', long, value_name = "OUTPUT", parse(from_os_str))] 52 output: Option<PathBuf>, 53 54 /// The path of the WebAssembly to compile 55 #[clap(index = 1, value_name = "MODULE", parse(from_os_str))] 56 module: PathBuf, 57 } 58 59 impl CompileCommand { 60 /// Executes the command. 61 pub fn execute(mut self) -> Result<()> { 62 self.common.init_logging(); 63 64 let target = self 65 .target 66 .take() 67 .unwrap_or_else(|| Triple::host().to_string()); 68 69 let config = self.common.config(Some(&target))?; 70 71 let engine = Engine::new(&config)?; 72 73 if self.module.file_name().is_none() { 74 bail!( 75 "'{}' is not a valid input module path", 76 self.module.display() 77 ); 78 } 79 80 let input = wat::parse_file(&self.module).with_context(|| "failed to read input file")?; 81 82 let output = self.output.take().unwrap_or_else(|| { 83 let mut output: PathBuf = self.module.file_name().unwrap().into(); 84 output.set_extension("cwasm"); 85 output 86 }); 87 88 // If the component-model proposal is enabled and the binary we're 89 // compiling looks like a component, tested by sniffing the first 8 90 // bytes with the current component model proposal. 91 #[cfg(feature = "component-model")] 92 { 93 if input.starts_with(b"\0asm\x0a\0\x01\0") { 94 fs::write(output, engine.precompile_component(&input)?)?; 95 return Ok(()); 96 } 97 } 98 fs::write(output, engine.precompile_module(&input)?)?; 99 100 Ok(()) 101 } 102 } 103 104 #[cfg(test)] 105 mod test { 106 use super::*; 107 use std::io::Write; 108 use tempfile::NamedTempFile; 109 use wasmtime::{Instance, Module, Store}; 110 111 #[test] 112 fn test_successful_compile() -> Result<()> { 113 let (mut input, input_path) = NamedTempFile::new()?.into_parts(); 114 input.write_all( 115 "(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(), 116 )?; 117 drop(input); 118 119 let output_path = NamedTempFile::new()?.into_temp_path(); 120 121 let command = CompileCommand::try_parse_from(vec![ 122 "compile", 123 "--disable-logging", 124 "-o", 125 output_path.to_str().unwrap(), 126 input_path.to_str().unwrap(), 127 ])?; 128 129 command.execute()?; 130 131 let engine = Engine::default(); 132 let contents = std::fs::read(output_path)?; 133 let module = unsafe { Module::deserialize(&engine, contents)? }; 134 let mut store = Store::new(&engine, ()); 135 let instance = Instance::new(&mut store, &module, &[])?; 136 let f = instance.get_typed_func::<i32, i32, _>(&mut store, "f")?; 137 assert_eq!(f.call(&mut store, 1234).unwrap(), 1234); 138 139 Ok(()) 140 } 141 142 #[cfg(target_arch = "x86_64")] 143 #[test] 144 fn test_x64_flags_compile() -> Result<()> { 145 let (mut input, input_path) = NamedTempFile::new()?.into_parts(); 146 input.write_all("(module)".as_bytes())?; 147 drop(input); 148 149 let output_path = NamedTempFile::new()?.into_temp_path(); 150 151 // Set all the x64 flags to make sure they work 152 let command = CompileCommand::try_parse_from(vec![ 153 "compile", 154 "--disable-logging", 155 "--cranelift-enable", 156 "has_sse3", 157 "--cranelift-enable", 158 "has_ssse3", 159 "--cranelift-enable", 160 "has_sse41", 161 "--cranelift-enable", 162 "has_sse42", 163 "--cranelift-enable", 164 "has_avx", 165 "--cranelift-enable", 166 "has_avx2", 167 "--cranelift-enable", 168 "has_fma", 169 "--cranelift-enable", 170 "has_avx512dq", 171 "--cranelift-enable", 172 "has_avx512vl", 173 "--cranelift-enable", 174 "has_avx512f", 175 "--cranelift-enable", 176 "has_popcnt", 177 "--cranelift-enable", 178 "has_bmi1", 179 "--cranelift-enable", 180 "has_bmi2", 181 "--cranelift-enable", 182 "has_lzcnt", 183 "-o", 184 output_path.to_str().unwrap(), 185 input_path.to_str().unwrap(), 186 ])?; 187 188 command.execute()?; 189 190 Ok(()) 191 } 192 193 #[cfg(target_arch = "aarch64")] 194 #[test] 195 fn test_aarch64_flags_compile() -> Result<()> { 196 let (mut input, input_path) = NamedTempFile::new()?.into_parts(); 197 input.write_all("(module)".as_bytes())?; 198 drop(input); 199 200 let output_path = NamedTempFile::new()?.into_temp_path(); 201 202 // Set all the aarch64 flags to make sure they work 203 let command = CompileCommand::try_parse_from(vec![ 204 "compile", 205 "--disable-logging", 206 "--cranelift-enable", 207 "has_lse", 208 "--cranelift-enable", 209 "has_pauth", 210 "--cranelift-enable", 211 "sign_return_address", 212 "--cranelift-enable", 213 "sign_return_address_all", 214 "--cranelift-enable", 215 "sign_return_address_with_bkey", 216 "-o", 217 output_path.to_str().unwrap(), 218 input_path.to_str().unwrap(), 219 ])?; 220 221 command.execute()?; 222 223 Ok(()) 224 } 225 226 #[cfg(target_arch = "x86_64")] 227 #[test] 228 fn test_unsupported_flags_compile() -> Result<()> { 229 let (mut input, input_path) = NamedTempFile::new()?.into_parts(); 230 input.write_all("(module)".as_bytes())?; 231 drop(input); 232 233 let output_path = NamedTempFile::new()?.into_temp_path(); 234 235 // aarch64 flags should not be supported 236 let command = CompileCommand::try_parse_from(vec![ 237 "compile", 238 "--disable-logging", 239 "--cranelift-enable", 240 "has_lse", 241 "-o", 242 output_path.to_str().unwrap(), 243 input_path.to_str().unwrap(), 244 ])?; 245 246 assert_eq!( 247 command.execute().unwrap_err().to_string(), 248 "No existing setting named 'has_lse'" 249 ); 250 251 Ok(()) 252 } 253 254 #[cfg(target_arch = "x86_64")] 255 #[test] 256 fn test_x64_presets_compile() -> Result<()> { 257 let (mut input, input_path) = NamedTempFile::new()?.into_parts(); 258 input.write_all("(module)".as_bytes())?; 259 drop(input); 260 261 let output_path = NamedTempFile::new()?.into_temp_path(); 262 263 for preset in &[ 264 "nehalem", 265 "haswell", 266 "broadwell", 267 "skylake", 268 "cannonlake", 269 "icelake", 270 "znver1", 271 ] { 272 let command = CompileCommand::try_parse_from(vec![ 273 "compile", 274 "--disable-logging", 275 "--cranelift-enable", 276 preset, 277 "-o", 278 output_path.to_str().unwrap(), 279 input_path.to_str().unwrap(), 280 ])?; 281 282 command.execute()?; 283 } 284 285 Ok(()) 286 } 287 } 288