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