cranelift_filetests/
test_run.rs

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
5use crate::function_runner::{CompiledTestFile, TestFileCompiler};
6use crate::runone::FileUpdate;
7use crate::subtest::{Context, SubTest};
8use anyhow::Context as _;
9use cranelift_codegen::data_value::DataValue;
10use cranelift_codegen::ir::Type;
11use cranelift_codegen::isa::{OwnedTargetIsa, TargetIsa};
12use cranelift_codegen::settings::{Configurable, Flags};
13use cranelift_codegen::{ir, settings};
14use cranelift_reader::TestCommand;
15use cranelift_reader::{parse_run_command, TestFile};
16use log::{info, trace};
17use std::borrow::Cow;
18use target_lexicon::Architecture;
19
20struct TestRun;
21
22pub 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`.
33fn 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.
51fn 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
145fn 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
176fn 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(&args))
195                })
196                .map_err(|s| anyhow::anyhow!("{}", s))?;
197        }
198    }
199    Ok(())
200}
201
202impl 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                "Cannot run runtests with pinned_reg enabled.",
230                "See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info"
231            ]
232            .join("\n")));
233        }
234
235        // Check that the host machine can run this test case (i.e. has all extensions)
236        let host_isa = build_host_isa(true, flags.clone(), vec![]).ok();
237        if let Err(e) = is_isa_compatible(file_path, host_isa.as_deref(), isa.unwrap()) {
238            log::info!("{}", e);
239            return Ok(());
240        }
241
242        let compiled_testfile = compile_testfile(&testfile, flags, isa.unwrap())?;
243
244        for (func, details) in &testfile.functions {
245            info!(
246                "Test: {}({}) {}",
247                self.name(),
248                func.name,
249                isa.map_or("-", TargetIsa::name)
250            );
251
252            let context = Context {
253                preamble_comments: &testfile.preamble_comments,
254                details,
255                flags,
256                isa,
257                file_path: file_path.as_ref(),
258                file_update,
259            };
260
261            run_test(&compiled_testfile, &func, &context).context(self.name())?;
262        }
263
264        Ok(())
265    }
266
267    fn run(&self, _func: Cow<ir::Function>, _context: &Context) -> anyhow::Result<()> {
268        unreachable!()
269    }
270}
271
272/// Build a VMContext struct with the layout described in docs/testing.md.
273pub fn build_vmctx_struct(ptr_ty: Type) -> (Vec<u64>, DataValue) {
274    let context_struct: Vec<u64> = Vec::new();
275
276    let ptr = context_struct.as_ptr() as usize as i128;
277    let ptr_dv =
278        DataValue::from_integer(ptr, ptr_ty).expect("Failed to cast pointer to native target size");
279
280    // Return all these to make sure we don't deallocate the heaps too early
281    (context_struct, ptr_dv)
282}