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
subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>>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`.
build_host_isa( infer_native_flags: bool, flags: settings::Flags, isa_flags: Vec<settings::Value>, ) -> anyhow::Result<OwnedTargetIsa>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.
is_isa_compatible( file_path: &str, host: Option<&dyn TargetIsa>, requested: &dyn TargetIsa, ) -> Result<(), String>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
compile_testfile( testfile: &TestFile, flags: &Flags, isa: &dyn TargetIsa, ) -> anyhow::Result<CompiledTestFile>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
run_test( testfile: &CompiledTestFile, func: &ir::Function, context: &Context, ) -> anyhow::Result<()>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 {
name(&self) -> &'static str203 fn name(&self) -> &'static str {
204 "run"
205 }
206
is_mutating(&self) -> bool207 fn is_mutating(&self) -> bool {
208 false
209 }
210
needs_isa(&self) -> bool211 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.
run_target<'a>( &self, testfile: &TestFile, file_update: &mut FileUpdate, file_path: &'a str, flags: &'a Flags, isa: Option<&'a dyn TargetIsa>, ) -> anyhow::Result<()>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
run(&self, _func: Cow<ir::Function>, _context: &Context) -> anyhow::Result<()>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.
build_vmctx_struct(ptr_ty: Type) -> (Vec<u64>, DataValue)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