Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/src/commands/compile.rs
1690 views
1
//! The module that implements the `wasmtime compile` command.
2
3
use anyhow::{Context, Result, bail};
4
use clap::Parser;
5
use std::fs;
6
use std::path::PathBuf;
7
use wasmtime::{CodeBuilder, CodeHint, Engine};
8
use wasmtime_cli_flags::CommonOptions;
9
10
const AFTER_HELP: &str =
11
"By default, no CPU features or presets will be enabled for the compilation.\n\
12
\n\
13
Usage examples:\n\
14
\n\
15
Compiling a WebAssembly module for the current platform:\n\
16
\n \
17
wasmtime compile example.wasm
18
\n\
19
Specifying the output file:\n\
20
\n \
21
wasmtime compile -o output.cwasm input.wasm\n\
22
\n\
23
Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\
24
\n \
25
wasmtime compile --target x86_64-unknown-linux -Ccranelift-skylake foo.wasm\n";
26
27
/// Compiles a WebAssembly module.
28
#[derive(Parser)]
29
#[command(
30
version,
31
after_help = AFTER_HELP,
32
)]
33
pub struct CompileCommand {
34
#[command(flatten)]
35
#[expect(missing_docs, reason = "don't want to mess with clap doc-strings")]
36
pub common: CommonOptions,
37
38
/// The path of the output compiled module; defaults to `<MODULE>.cwasm`
39
#[arg(short = 'o', long, value_name = "OUTPUT")]
40
pub output: Option<PathBuf>,
41
42
/// The directory path to write clif files into, one clif file per wasm function.
43
#[arg(long = "emit-clif", value_name = "PATH")]
44
pub emit_clif: Option<PathBuf>,
45
46
/// The path of the WebAssembly to compile
47
#[arg(index = 1, value_name = "MODULE")]
48
pub module: PathBuf,
49
}
50
51
impl CompileCommand {
52
/// Executes the command.
53
pub fn execute(mut self) -> Result<()> {
54
self.common.init_logging()?;
55
56
let mut config = self.common.config(None)?;
57
58
if let Some(path) = self.emit_clif {
59
if !path.exists() {
60
std::fs::create_dir(&path)?;
61
}
62
63
if !path.is_dir() {
64
bail!(
65
"the path passed for '--emit-clif' ({}) must be a directory",
66
path.display()
67
);
68
}
69
70
config.emit_clif(&path);
71
}
72
73
let engine = Engine::new(&config)?;
74
75
if self.module.file_name().is_none() {
76
bail!(
77
"'{}' is not a valid input module path",
78
self.module.display()
79
);
80
}
81
82
let mut code = CodeBuilder::new(&engine);
83
code.wasm_binary_or_text_file(&self.module)?;
84
85
let output = self.output.take().unwrap_or_else(|| {
86
let mut output: PathBuf = self.module.file_name().unwrap().into();
87
output.set_extension("cwasm");
88
output
89
});
90
91
let output_bytes = match code.hint() {
92
#[cfg(feature = "component-model")]
93
Some(CodeHint::Component) => code.compile_component_serialized()?,
94
#[cfg(not(feature = "component-model"))]
95
Some(CodeHint::Component) => {
96
bail!("component model support was disabled at compile time")
97
}
98
Some(CodeHint::Module) | None => code.compile_module_serialized()?,
99
};
100
fs::write(&output, output_bytes)
101
.with_context(|| format!("failed to write output: {}", output.display()))?;
102
103
Ok(())
104
}
105
}
106
107
#[cfg(all(test, not(miri)))]
108
mod test {
109
use super::*;
110
use std::io::Write;
111
use tempfile::NamedTempFile;
112
use wasmtime::{Instance, Module, Store};
113
114
#[test]
115
fn test_successful_compile() -> Result<()> {
116
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
117
input.write_all(
118
"(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(),
119
)?;
120
drop(input);
121
122
let output_path = NamedTempFile::new()?.into_temp_path();
123
124
let command = CompileCommand::try_parse_from(vec![
125
"compile",
126
"-Dlogging=n",
127
"-o",
128
output_path.to_str().unwrap(),
129
input_path.to_str().unwrap(),
130
])?;
131
132
command.execute()?;
133
134
let engine = Engine::default();
135
let contents = std::fs::read(output_path)?;
136
let module = unsafe { Module::deserialize(&engine, contents)? };
137
let mut store = Store::new(&engine, ());
138
let instance = Instance::new(&mut store, &module, &[])?;
139
let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?;
140
assert_eq!(f.call(&mut store, 1234).unwrap(), 1234);
141
142
Ok(())
143
}
144
145
#[cfg(target_arch = "x86_64")]
146
#[test]
147
fn test_x64_flags_compile() -> Result<()> {
148
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
149
input.write_all("(module)".as_bytes())?;
150
drop(input);
151
152
let output_path = NamedTempFile::new()?.into_temp_path();
153
154
// Set all the x64 flags to make sure they work
155
let command = CompileCommand::try_parse_from(vec![
156
"compile",
157
"-Dlogging=n",
158
"-Ccranelift-has-sse3",
159
"-Ccranelift-has-ssse3",
160
"-Ccranelift-has-sse41",
161
"-Ccranelift-has-sse42",
162
"-Ccranelift-has-avx",
163
"-Ccranelift-has-avx2",
164
"-Ccranelift-has-fma",
165
"-Ccranelift-has-avx512dq",
166
"-Ccranelift-has-avx512vl",
167
"-Ccranelift-has-avx512f",
168
"-Ccranelift-has-popcnt",
169
"-Ccranelift-has-bmi1",
170
"-Ccranelift-has-bmi2",
171
"-Ccranelift-has-lzcnt",
172
"-o",
173
output_path.to_str().unwrap(),
174
input_path.to_str().unwrap(),
175
])?;
176
177
command.execute()?;
178
179
Ok(())
180
}
181
182
#[cfg(target_arch = "aarch64")]
183
#[test]
184
fn test_aarch64_flags_compile() -> Result<()> {
185
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
186
input.write_all("(module)".as_bytes())?;
187
drop(input);
188
189
let output_path = NamedTempFile::new()?.into_temp_path();
190
191
// Set all the aarch64 flags to make sure they work
192
let command = CompileCommand::try_parse_from(vec![
193
"compile",
194
"-Dlogging=n",
195
"-Ccranelift-has-lse",
196
"-Ccranelift-has-pauth",
197
"-Ccranelift-has-fp16",
198
"-Ccranelift-sign-return-address",
199
"-Ccranelift-sign-return-address-all",
200
"-Ccranelift-sign-return-address-with-bkey",
201
"-o",
202
output_path.to_str().unwrap(),
203
input_path.to_str().unwrap(),
204
])?;
205
206
command.execute()?;
207
208
Ok(())
209
}
210
211
#[cfg(target_arch = "x86_64")]
212
#[test]
213
fn test_unsupported_flags_compile() -> Result<()> {
214
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
215
input.write_all("(module)".as_bytes())?;
216
drop(input);
217
218
let output_path = NamedTempFile::new()?.into_temp_path();
219
220
// aarch64 flags should not be supported
221
let command = CompileCommand::try_parse_from(vec![
222
"compile",
223
"-Dlogging=n",
224
"-Ccranelift-has-lse",
225
"-o",
226
output_path.to_str().unwrap(),
227
input_path.to_str().unwrap(),
228
])?;
229
230
assert_eq!(
231
command.execute().unwrap_err().to_string(),
232
"No existing setting named 'has_lse'"
233
);
234
235
Ok(())
236
}
237
238
#[cfg(target_arch = "x86_64")]
239
#[test]
240
fn test_x64_presets_compile() -> Result<()> {
241
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
242
input.write_all("(module)".as_bytes())?;
243
drop(input);
244
245
let output_path = NamedTempFile::new()?.into_temp_path();
246
247
for preset in &[
248
"nehalem",
249
"haswell",
250
"broadwell",
251
"skylake",
252
"cannonlake",
253
"icelake",
254
"znver1",
255
] {
256
let flag = format!("-Ccranelift-{preset}");
257
let command = CompileCommand::try_parse_from(vec![
258
"compile",
259
"-Dlogging=n",
260
flag.as_str(),
261
"-o",
262
output_path.to_str().unwrap(),
263
input_path.to_str().unwrap(),
264
])?;
265
266
command.execute()?;
267
}
268
269
Ok(())
270
}
271
}
272
273