Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/filetests/src/test_run.rs
1691 views
1
//! Test command for running CLIF files and verifying their results
2
//!
3
//! The `run` test command compiles each function on the host machine and executes it
4
5
use crate::function_runner::{CompiledTestFile, TestFileCompiler};
6
use crate::runone::FileUpdate;
7
use crate::subtest::{Context, SubTest};
8
use anyhow::Context as _;
9
use cranelift_codegen::data_value::DataValue;
10
use cranelift_codegen::ir::Type;
11
use cranelift_codegen::isa::{OwnedTargetIsa, TargetIsa};
12
use cranelift_codegen::settings::{Configurable, Flags};
13
use cranelift_codegen::{ir, settings};
14
use cranelift_reader::TestCommand;
15
use cranelift_reader::{TestFile, parse_run_command};
16
use log::{info, trace};
17
use std::borrow::Cow;
18
use target_lexicon::Architecture;
19
20
struct TestRun;
21
22
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
23
assert_eq!(parsed.command, "run");
24
if !parsed.options.is_empty() {
25
anyhow::bail!("No options allowed on {}", parsed);
26
}
27
Ok(Box::new(TestRun))
28
}
29
30
/// Builds a [TargetIsa] for the current host.
31
///
32
/// ISA Flags can be overridden by passing [Value]'s via `isa_flags`.
33
fn build_host_isa(
34
infer_native_flags: bool,
35
flags: settings::Flags,
36
isa_flags: Vec<settings::Value>,
37
) -> anyhow::Result<OwnedTargetIsa> {
38
let mut builder = cranelift_native::builder_with_options(infer_native_flags)
39
.map_err(|e| anyhow::Error::msg(e))?;
40
41
// Copy ISA Flags
42
for value in isa_flags {
43
builder.set(value.name, &value.value_string())?;
44
}
45
46
let isa = builder.finish(flags)?;
47
Ok(isa)
48
}
49
50
/// Checks if the host's ISA is compatible with the one requested by the test.
51
fn is_isa_compatible(
52
file_path: &str,
53
host: Option<&dyn TargetIsa>,
54
requested: &dyn TargetIsa,
55
) -> Result<(), String> {
56
let host_triple = match host {
57
Some(host) => host.triple().clone(),
58
None => target_lexicon::Triple::host(),
59
};
60
// If this test requests to run on a completely different
61
// architecture than the host platform then we skip it entirely,
62
// since we won't be able to natively execute machine code.
63
let host_arch = host_triple.architecture;
64
let requested_arch = requested.triple().architecture;
65
66
match (host_arch, requested_arch) {
67
// If the host matches the requested target, then that's all good.
68
(host, requested) if host == requested => {}
69
70
// Allow minor differences in risc-v targets.
71
(Architecture::Riscv64(_), Architecture::Riscv64(_)) => {}
72
73
// Any host can run pulley so long as the pointer width and endianness
74
// match.
75
(
76
_,
77
Architecture::Pulley32
78
| Architecture::Pulley64
79
| Architecture::Pulley32be
80
| Architecture::Pulley64be,
81
) if host_triple.pointer_width() == requested.triple().pointer_width()
82
&& host_triple.endianness() == requested.triple().endianness() => {}
83
84
_ => {
85
return Err(format!(
86
"skipped {file_path}: host can't run {requested_arch:?} programs"
87
));
88
}
89
}
90
91
// We need to check that the requested ISA does not have any flags that
92
// we can't natively support on the host.
93
let requested_flags = requested.isa_flags();
94
for req_value in requested_flags {
95
// pointer_width for pulley already validated above
96
if req_value.name == "pointer_width" {
97
continue;
98
}
99
let requested = match req_value.as_bool() {
100
Some(requested) => requested,
101
None => unimplemented!("ISA flag {} of kind {:?}", req_value.name, req_value.kind()),
102
};
103
let host_isa_flags = match host {
104
Some(host) => host.isa_flags(),
105
None => {
106
return Err(format!(
107
"host not available on this platform for isa-specific flag"
108
));
109
}
110
};
111
let available_in_host = host_isa_flags
112
.iter()
113
.find(|val| val.name == req_value.name)
114
.and_then(|val| val.as_bool())
115
.unwrap_or(false);
116
117
if !requested || available_in_host {
118
continue;
119
}
120
121
// The AArch64 feature `sign_return_address` is supported on all AArch64
122
// hosts, regardless of whether `cranelift-native` infers it or not. The
123
// instructions emitted with this feature enabled are interpreted as
124
// "hint" noop instructions on CPUs which don't support address
125
// authentication.
126
//
127
// Note that at this time `cranelift-native` will only enable
128
// `sign_return_address` for macOS (notably not Linux) because of a
129
// historical bug in libunwind which causes pointer address signing,
130
// when run on hardware that supports it, so segfault during unwinding.
131
if req_value.name == "sign_return_address" && matches!(host_arch, Architecture::Aarch64(_))
132
{
133
continue;
134
}
135
136
return Err(format!(
137
"skipped {}: host does not support ISA flag {}",
138
file_path, req_value.name
139
));
140
}
141
142
Ok(())
143
}
144
145
fn compile_testfile(
146
testfile: &TestFile,
147
flags: &Flags,
148
isa: &dyn TargetIsa,
149
) -> anyhow::Result<CompiledTestFile> {
150
let isa = match isa.triple().architecture {
151
// Convert `&dyn TargetIsa` to `OwnedTargetIsa` by re-making the ISA and
152
// applying pulley flags/etc.
153
Architecture::Pulley32
154
| Architecture::Pulley64
155
| Architecture::Pulley32be
156
| Architecture::Pulley64be => {
157
let mut builder = cranelift_codegen::isa::lookup(isa.triple().clone())?;
158
for value in isa.isa_flags() {
159
builder.set(value.name, &value.value_string()).unwrap();
160
}
161
builder.finish(flags.clone())?
162
}
163
164
// We can't use the requested ISA directly since it does not contain info
165
// about the operating system / calling convention / etc..
166
//
167
// Copy the requested ISA flags into the host ISA and use that.
168
_ => build_host_isa(false, flags.clone(), isa.isa_flags()).unwrap(),
169
};
170
171
let mut tfc = TestFileCompiler::new(isa);
172
tfc.add_testfile(testfile)?;
173
Ok(tfc.compile()?)
174
}
175
176
fn run_test(
177
testfile: &CompiledTestFile,
178
func: &ir::Function,
179
context: &Context,
180
) -> anyhow::Result<()> {
181
for comment in context.details.comments.iter() {
182
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
183
trace!("Parsed run command: {command}");
184
185
command
186
.run(|_, run_args| {
187
let (_ctx_struct, _vmctx_ptr) =
188
build_vmctx_struct(context.isa.unwrap().pointer_type());
189
190
let mut args = Vec::with_capacity(run_args.len());
191
args.extend_from_slice(run_args);
192
193
let trampoline = testfile.get_trampoline(func).unwrap();
194
Ok(trampoline.call(&testfile, &args))
195
})
196
.map_err(|s| anyhow::anyhow!("{}", s))?;
197
}
198
}
199
Ok(())
200
}
201
202
impl SubTest for TestRun {
203
fn name(&self) -> &'static str {
204
"run"
205
}
206
207
fn is_mutating(&self) -> bool {
208
false
209
}
210
211
fn needs_isa(&self) -> bool {
212
true
213
}
214
215
/// Runs the entire subtest for a given target, invokes [Self::run] for running
216
/// individual tests.
217
fn run_target<'a>(
218
&self,
219
testfile: &TestFile,
220
file_update: &mut FileUpdate,
221
file_path: &'a str,
222
flags: &'a Flags,
223
isa: Option<&'a dyn TargetIsa>,
224
) -> anyhow::Result<()> {
225
// Disable runtests with pinned reg enabled.
226
// We've had some abi issues that the trampoline isn't quite ready for.
227
if flags.enable_pinned_reg() {
228
return Err(anyhow::anyhow!(
229
[
230
"Cannot run runtests with pinned_reg enabled.",
231
"See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info"
232
]
233
.join("\n")
234
));
235
}
236
237
// Check that the host machine can run this test case (i.e. has all extensions)
238
let host_isa = build_host_isa(true, flags.clone(), vec![]).ok();
239
if let Err(e) = is_isa_compatible(file_path, host_isa.as_deref(), isa.unwrap()) {
240
log::info!("{e}");
241
return Ok(());
242
}
243
244
let compiled_testfile = compile_testfile(&testfile, flags, isa.unwrap())?;
245
246
for (func, details) in &testfile.functions {
247
info!(
248
"Test: {}({}) {}",
249
self.name(),
250
func.name,
251
isa.map_or("-", TargetIsa::name)
252
);
253
254
let context = Context {
255
preamble_comments: &testfile.preamble_comments,
256
details,
257
flags,
258
isa,
259
file_path: file_path.as_ref(),
260
file_update,
261
};
262
263
run_test(&compiled_testfile, &func, &context).context(self.name())?;
264
}
265
266
Ok(())
267
}
268
269
fn run(&self, _func: Cow<ir::Function>, _context: &Context) -> anyhow::Result<()> {
270
unreachable!()
271
}
272
}
273
274
/// Build a VMContext struct with the layout described in docs/testing.md.
275
pub fn build_vmctx_struct(ptr_ty: Type) -> (Vec<u64>, DataValue) {
276
let context_struct: Vec<u64> = Vec::new();
277
278
let ptr = context_struct.as_ptr() as usize as i128;
279
let ptr_dv =
280
DataValue::from_integer(ptr, ptr_ty).expect("Failed to cast pointer to native target size");
281
282
// Return all these to make sure we don't deallocate the heaps too early
283
(context_struct, ptr_dv)
284
}
285
286