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