Files
wasmtime/cranelift/filetests/src/test_run.rs
Afonso Bordado 86331b9b37 cranelift: Native feature detection for RISC-V (#5044)
* cranelift: Native feature detection for RISC-V

* cranelift: Typo fix

Thanks @cfallin
2022-10-11 19:29:03 +00:00

237 lines
7.6 KiB
Rust

//! Test command for running CLIF files and verifying their results
//!
//! The `run` test command compiles each function on the host machine and executes it
use crate::function_runner::{CompiledTestFile, TestFileCompiler};
use crate::runone::FileUpdate;
use crate::runtest_environment::{HeapMemory, RuntestEnvironment};
use crate::subtest::{Context, SubTest};
use anyhow::Context as _;
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::Type;
use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings::{Configurable, Flags};
use cranelift_codegen::{ir, settings};
use cranelift_reader::TestCommand;
use cranelift_reader::{parse_run_command, TestFile};
use log::{info, trace};
use std::borrow::Cow;
use target_lexicon::Architecture;
struct TestRun;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "run");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestRun))
}
/// Builds a [TargetIsa] for the current host.
///
/// ISA Flags can be overridden by passing [Value]'s via `isa_flags`.
fn build_host_isa(
infer_native_flags: bool,
flags: settings::Flags,
isa_flags: Vec<settings::Value>,
) -> Box<dyn TargetIsa> {
let mut builder = cranelift_native::builder_with_options(infer_native_flags)
.expect("Unable to build a TargetIsa for the current host");
// Copy ISA Flags
for value in isa_flags {
builder.set(value.name, &value.value_string()).unwrap();
}
builder.finish(flags).unwrap()
}
/// Checks if the host's ISA is compatible with the one requested by the test.
fn is_isa_compatible(
file_path: &str,
host: &dyn TargetIsa,
requested: &dyn TargetIsa,
) -> Result<(), String> {
// If this test requests to run on a completely different
// architecture than the host platform then we skip it entirely,
// since we won't be able to natively execute machine code.
let host_arch = host.triple().architecture;
let requested_arch = requested.triple().architecture;
match (host_arch, requested_arch) {
(host, requested) if host == requested => {}
(Architecture::Riscv64(_), Architecture::Riscv64(_)) => {}
_ => {
return Err(format!(
"skipped {}: host can't run {:?} programs",
file_path, requested_arch
))
}
}
// We need to check that the requested ISA does not have any flags that
// we can't natively support on the host.
let requested_flags = requested.isa_flags();
for req_value in requested_flags {
if let Some(requested) = req_value.as_bool() {
let available_in_host = host
.isa_flags()
.iter()
.find(|val| val.name == req_value.name)
.and_then(|val| val.as_bool())
.unwrap_or(false);
if requested && !available_in_host {
return Err(format!(
"skipped {}: host does not support ISA flag {}",
file_path, req_value.name
));
}
} else {
unimplemented!("ISA flag {} of kind {:?}", req_value.name, req_value.kind());
}
}
Ok(())
}
fn compile_testfile(
testfile: &TestFile,
flags: &Flags,
isa: &dyn TargetIsa,
) -> anyhow::Result<CompiledTestFile> {
// We can't use the requested ISA directly since it does not contain info
// about the operating system / calling convention / etc..
//
// Copy the requested ISA flags into the host ISA and use that.
let isa = build_host_isa(false, flags.clone(), isa.isa_flags());
let mut tfc = TestFileCompiler::new(isa);
tfc.add_testfile(testfile)?;
Ok(tfc.compile()?)
}
fn run_test(
testfile: &CompiledTestFile,
func: &ir::Function,
context: &Context,
) -> anyhow::Result<()> {
let test_env = RuntestEnvironment::parse(&context.details.comments[..])?;
for comment in context.details.comments.iter() {
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
trace!("Parsed run command: {}", command);
command
.run(|_, run_args| {
test_env.validate_signature(&func)?;
let (_heaps, _ctx_struct, vmctx_ptr) =
build_vmctx_struct(&test_env, context.isa.unwrap().pointer_type());
let mut args = Vec::with_capacity(run_args.len());
if test_env.is_active() {
args.push(vmctx_ptr);
}
args.extend_from_slice(run_args);
let trampoline = testfile.get_trampoline(func).unwrap();
Ok(trampoline.call(&args))
})
.map_err(|s| anyhow::anyhow!("{}", s))?;
}
}
Ok(())
}
impl SubTest for TestRun {
fn name(&self) -> &'static str {
"run"
}
fn is_mutating(&self) -> bool {
false
}
fn needs_isa(&self) -> bool {
true
}
/// Runs the entire subtest for a given target, invokes [Self::run] for running
/// individual tests.
fn run_target<'a>(
&self,
testfile: &TestFile,
file_update: &mut FileUpdate,
file_path: &'a str,
flags: &'a Flags,
isa: Option<&'a dyn TargetIsa>,
) -> anyhow::Result<()> {
// Disable runtests with pinned reg enabled.
// We've had some abi issues that the trampoline isn't quite ready for.
if flags.enable_pinned_reg() {
return Err(anyhow::anyhow!([
"Cannot run runtests with pinned_reg enabled.",
"See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info"
]
.join("\n")));
}
// Check that the host machine can run this test case (i.e. has all extensions)
let host_isa = build_host_isa(true, flags.clone(), vec![]);
if let Err(e) = is_isa_compatible(file_path, host_isa.as_ref(), isa.unwrap()) {
log::info!("{}", e);
return Ok(());
}
let compiled_testfile = compile_testfile(&testfile, flags, isa.unwrap())?;
for (func, details) in &testfile.functions {
info!(
"Test: {}({}) {}",
self.name(),
func.name,
isa.map_or("-", TargetIsa::name)
);
let context = Context {
preamble_comments: &testfile.preamble_comments,
details,
flags,
isa,
file_path: file_path.as_ref(),
file_update,
};
run_test(&compiled_testfile, &func, &context).context(self.name())?;
}
Ok(())
}
fn run(&self, _func: Cow<ir::Function>, _context: &Context) -> anyhow::Result<()> {
unreachable!()
}
}
/// Build a VMContext struct with the layout described in docs/testing.md.
pub fn build_vmctx_struct(
test_env: &RuntestEnvironment,
ptr_ty: Type,
) -> (Vec<HeapMemory>, Vec<u64>, DataValue) {
let heaps = test_env.allocate_memory();
let context_struct: Vec<u64> = heaps
.iter()
.flat_map(|heap| [heap.as_ptr(), heap.as_ptr().wrapping_add(heap.len())])
.map(|p| p as usize as u64)
.collect();
let ptr = context_struct.as_ptr() as usize as i128;
let ptr_dv =
DataValue::from_integer(ptr, ptr_ty).expect("Failed to cast pointer to native target size");
// Return all these to make sure we don't deallocate the heaps too early
(heaps, context_struct, ptr_dv)
}