xref: /wasmtime-44.0.1/src/commands/compile.rs (revision cd53bed8)
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