Add ability to call CLIF functions with arbitrary arguments in filetests

This resolves the work started in https://github.com/bytecodealliance/cranelift/pull/1231 and https://github.com/bytecodealliance/wasmtime/pull/1436. Cranelift filetests currently have the ability to run CLIF functions with a signature like `() -> b*` and check that the result is true under the `test run` directive. This PR adds the ability to call functions with arbitrary arguments and non-boolean returns and either print the result or check against a list of expected results:
 - `run` commands look like `; run: %add(2, 2) == 4` or `; run: %add(2, 2) != 5` and verify that the executed CLIF function returns the expected value
 - `print` commands look like `; print: %add(2, 2)` and print the result of the function to stdout

To make this work, this PR compiles a single Cranelift `Function` into a `CompiledFunction` using a `SingleFunctionCompiler`. Because we will not know the signature of the function until runtime, we use a `Trampoline` to place the values in the appropriate location for the calling convention; this should look a lot like what @alexcrichton is doing with `VMTrampoline` in wasmtime (see 3b7cb6ee64/crates/api/src/func.rs (L510-L526), 3b7cb6ee64/crates/jit/src/compiler.rs (L260)). To avoid re-compiling `Trampoline`s for the same function signatures, `Trampoline`s are cached in the `SingleFunctionCompiler`.
This commit is contained in:
Andrew Brown
2020-04-15 13:50:51 -07:00
parent 2048d3d30c
commit 38dff29179
8 changed files with 510 additions and 93 deletions

View File

@@ -7,8 +7,8 @@
//! - `; run: %fn(42, 4.2) == false`: this syntax specifies the parameters and return values.
use cranelift_codegen::ir::immediates::{Ieee32, Ieee64};
use cranelift_codegen::ir::ConstantData;
use std::fmt::{Display, Formatter, Result};
use cranelift_codegen::ir::{self, ConstantData, Type};
use std::fmt::{self, Display, Formatter};
/// A run command appearing in a test file.
///
@@ -22,8 +22,37 @@ pub enum RunCommand {
Run(Invocation, Comparison, Vec<DataValue>),
}
impl RunCommand {
/// Run the [RunCommand]:
/// - for [RunCommand::Print], print the returned values from invoking the function.
/// - for [RunCommand::Run], compare the returned values from the invoked function and
/// return an `Err` with a descriptive string if the comparison fails.
pub fn run<F>(&self, invoke_fn: F) -> Result<(), String>
where
F: FnOnce(&[DataValue]) -> Vec<DataValue>,
{
match self {
RunCommand::Print(invoke) => {
let actual = invoke_fn(&invoke.args);
println!("{:?} -> {:?}", invoke, actual)
}
RunCommand::Run(invoke, compare, expected) => {
let actual = invoke_fn(&invoke.args);
let matched = match compare {
Comparison::Equals => *expected == actual,
Comparison::NotEquals => *expected != actual,
};
if !matched {
return Err(format!("Failed test: {:?}, actual: {:?}", self, actual));
}
}
}
Ok(())
}
}
impl Display for RunCommand {
fn fmt(&self, f: &mut Formatter) -> Result {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
RunCommand::Print(invocation) => write!(f, "print: {}", invocation),
RunCommand::Run(invocation, comparison, expected) => {
@@ -58,7 +87,7 @@ impl Invocation {
}
impl Display for Invocation {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "%{}(", self.func)?;
write_data_value_list(f, &self.args)?;
write!(f, ")")
@@ -82,6 +111,22 @@ pub enum DataValue {
V128([u8; 16]),
}
impl DataValue {
/// Return the Cranelift IR [Type] for this [DataValue].
pub fn ty(&self) -> Type {
match self {
DataValue::B(_) => ir::types::B8,
DataValue::I8(_) => ir::types::I8,
DataValue::I16(_) => ir::types::I16,
DataValue::I32(_) => ir::types::I32,
DataValue::I64(_) => ir::types::I64,
DataValue::F32(_) => ir::types::F32,
DataValue::F64(_) => ir::types::F64,
DataValue::V128(_) => ir::types::I8X16,
}
}
}
/// Helper for creating [From] implementations for [DataValue]
macro_rules! from_data {
( $ty:ty, $variant:ident ) => {
@@ -102,7 +147,7 @@ from_data!(f64, F64);
from_data!([u8; 16], V128);
impl Display for DataValue {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
DataValue::B(dv) => write!(f, "{}", dv),
DataValue::I8(dv) => write!(f, "{}", dv),
@@ -119,7 +164,7 @@ impl Display for DataValue {
}
/// Helper function for displaying `Vec<DataValue>`.
fn write_data_value_list(f: &mut Formatter<'_>, list: &[DataValue]) -> Result {
fn write_data_value_list(f: &mut Formatter<'_>, list: &[DataValue]) -> fmt::Result {
match list.len() {
0 => Ok(()),
1 => write!(f, "{}", list[0]),
@@ -142,10 +187,30 @@ pub enum Comparison {
}
impl Display for Comparison {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Comparison::Equals => write!(f, "=="),
Comparison::NotEquals => write!(f, "!="),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::parse_run_command;
use cranelift_codegen::ir::{types, AbiParam, Signature};
use cranelift_codegen::isa::CallConv;
#[test]
fn run_a_command() {
let mut signature = Signature::new(CallConv::Fast);
signature.returns.push(AbiParam::new(types::I32));
let command = parse_run_command(";; run: %return42() == 42 ", &signature)
.unwrap()
.unwrap();
assert!(command.run(|_| vec![DataValue::I32(42)]).is_ok());
assert!(command.run(|_| vec![DataValue::I32(43)]).is_err());
}
}