cranelift: Allow call and call_indirect in runtests (#4667)
* cranelift: Change test runner order Changes the ordering of runtests to run per target and then per function. This change doesn't do a lot by itself, but helps future refactorings of runtests. * cranelift: Rename SingleFunctionCompiler to TestCaseCompiler * cranelift: Skip runtests per target instead of per run * cranelift: Deduplicate test names With the upcoming changes to the runtest infrastructure we require unique ExtNames for all tests. Note that for test names we have a 16 character limit on test names, and must be unique within those 16 characters. * cranelift: Add TestFileCompiler to runtests TestFileCompiler allows us to compile the entire file once, and then call the trampolines for each test. The previous code was compiling the function for each invocation of a test. * cranelift: Deduplicate ExtName for avg_round tests * cranelift: Rename functions as they are defined. The JIT internally only deals with User functions, and cannot link test name funcs. This also caches trampolines by signature. * cranelift: Preserve original name when reporting errors. * cranelift: Rename aarch64 test functions * cranelift: Add `call` and `call_indirect` tests! * cranelift: Add pauth runtests for aarch64 * cranelift: Rename duplicate s390x tests * cranelift: Delete `i128_bricmp_of` function from i128-bricmp It looks like we forgot to delete it when it was moved to `i128-bricmp-overflow`, and since it didn't have a run invocation it was never compiled. However, s390x does not support this, and panics when lowering. * cranelift: Add `colocated` call tests * cranelift: Rename *more* `s390x` tests * cranelift: Add pauth + sign_return_address call tests * cranelift: Undeduplicate test names With the latest main changes we now support *unlimited* length test names. This commit reverts: 52274676ff631c630f9879dd32e756566d3e700f 7989edc172493547cdf63e180bb58365e8a43a42 25c8a8395527d98976be6a34baa3b0b214776739 792e8cfa8f748077f9d80fe7ee5e958b7124e83b * cranelift: Add LibCall tests * cranelift: Revert more test names These weren't auto reverted by the previous revert. * cranelift: Disable libcall tests for aarch64 * cranelift: Runtest fibonacci tests * cranelift: Misc cleanup
This commit is contained in:
@@ -21,7 +21,7 @@ use super::function::FunctionParameters;
|
|||||||
/// This is used both for naming a function (for debugging purposes) and for declaring external
|
/// This is used both for naming a function (for debugging purposes) and for declaring external
|
||||||
/// functions. In the latter case, this becomes an `ExternalName`, which gets embedded in
|
/// functions. In the latter case, this becomes an `ExternalName`, which gets embedded in
|
||||||
/// relocations later, etc.
|
/// relocations later, etc.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
||||||
pub enum UserFuncName {
|
pub enum UserFuncName {
|
||||||
/// A user-defined name, with semantics left to the user.
|
/// A user-defined name, with semantics left to the user.
|
||||||
@@ -39,7 +39,7 @@ impl UserFuncName {
|
|||||||
|
|
||||||
/// Create a new external name from a user-defined external function reference.
|
/// Create a new external name from a user-defined external function reference.
|
||||||
pub fn user(namespace: u32, index: u32) -> Self {
|
pub fn user(namespace: u32, index: u32) -> Self {
|
||||||
Self::User(UserExternalName { namespace, index })
|
Self::User(UserExternalName::new(namespace, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +70,13 @@ pub struct UserExternalName {
|
|||||||
pub index: u32,
|
pub index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UserExternalName {
|
||||||
|
/// Creates a new [UserExternalName].
|
||||||
|
pub fn new(namespace: u32, index: u32) -> Self {
|
||||||
|
Self { namespace, index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for UserExternalName {
|
impl fmt::Display for UserExternalName {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "u{}:{}", self.namespace, self.index)
|
write!(f, "u{}:{}", self.namespace, self.index)
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ block0(v0: i32, v1: i64, v2: i16):
|
|||||||
v6 = load.i32 big v3
|
v6 = load.i32 big v3
|
||||||
return v6
|
return v6
|
||||||
}
|
}
|
||||||
; run: %atomic_rmw_add_little_i16(0x12345678, 0, 0x1111) == 0x23455678
|
; run: %atomic_rmw_add_big_i16(0x12345678, 0, 0x1111) == 0x23455678
|
||||||
; run: %atomic_rmw_add_little_i16(0x12345678, 0, 0xffff) == 0x12335678
|
; run: %atomic_rmw_add_big_i16(0x12345678, 0, 0xffff) == 0x12335678
|
||||||
; run: %atomic_rmw_add_little_i16(0x12345678, 2, 0x1111) == 0x12346789
|
; run: %atomic_rmw_add_big_i16(0x12345678, 2, 0x1111) == 0x12346789
|
||||||
; run: %atomic_rmw_add_little_i16(0x12345678, 2, 0xffff) == 0x12345677
|
; run: %atomic_rmw_add_big_i16(0x12345678, 2, 0xffff) == 0x12345677
|
||||||
|
|
||||||
function %atomic_rmw_add_big_i8(i32, i64, i8) -> i32 {
|
function %atomic_rmw_add_big_i8(i32, i64, i8) -> i32 {
|
||||||
ss0 = explicit_slot 4
|
ss0 = explicit_slot 4
|
||||||
|
|||||||
68
cranelift/filetests/filetests/runtests/call.clif
Normal file
68
cranelift/filetests/filetests/runtests/call.clif
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
test run
|
||||||
|
target x86_64
|
||||||
|
target aarch64
|
||||||
|
target aarch64 sign_return_address
|
||||||
|
target aarch64 has_pauth sign_return_address
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
|
||||||
|
function %callee_i64(i64) -> i64 {
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = iadd_imm.i64 v0, 10
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
function %call_i64(i64) -> i64 {
|
||||||
|
fn0 = %callee_i64(i64) -> i64
|
||||||
|
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %call_i64(10) == 20
|
||||||
|
|
||||||
|
function %colocated_i64(i64) -> i64 {
|
||||||
|
fn0 = colocated %callee_i64(i64) -> i64
|
||||||
|
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %colocated_i64(10) == 20
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function %callee_f64(f64) -> f64 {
|
||||||
|
block0(v0: f64):
|
||||||
|
v1 = f64const 0x10.0
|
||||||
|
v2 = fadd.f64 v0, v1
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
|
||||||
|
function %call_f64(f64) -> f64 {
|
||||||
|
fn0 = %callee_f64(f64) -> f64
|
||||||
|
|
||||||
|
block0(v0: f64):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %call_f64(0x10.0) == 0x20.0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function %callee_b1(b1) -> b1 {
|
||||||
|
block0(v0: b1):
|
||||||
|
v1 = bnot.b1 v0
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
function %call_b1(b1) -> b1 {
|
||||||
|
fn0 = %callee_b1(b1) -> b1
|
||||||
|
|
||||||
|
block0(v0: b1):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %call_b1(true) == false
|
||||||
|
; run: %call_b1(false) == true
|
||||||
36
cranelift/filetests/filetests/runtests/call_indirect.clif
Normal file
36
cranelift/filetests/filetests/runtests/call_indirect.clif
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
test run
|
||||||
|
target x86_64
|
||||||
|
target aarch64
|
||||||
|
target aarch64 sign_return_address
|
||||||
|
target aarch64 has_pauth sign_return_address
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
|
||||||
|
function %callee_indirect(i64) -> i64 {
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = iadd_imm.i64 v0, 10
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
function %call_ind(i64) -> i64 {
|
||||||
|
fn0 = %callee_indirect(i64) -> i64
|
||||||
|
; sig0 = (i64) -> i64
|
||||||
|
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = func_addr.i64 fn0
|
||||||
|
v2 = call_indirect.i64 sig0, v1(v0)
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
; run: %call_ind(10) == 20
|
||||||
|
|
||||||
|
|
||||||
|
function %call_ind_colocated(i64) -> i64 {
|
||||||
|
fn0 = colocated %callee_indirect(i64) -> i64
|
||||||
|
; sig0 = (i64) -> i64
|
||||||
|
|
||||||
|
block0(v0: i64):
|
||||||
|
v1 = func_addr.i64 fn0
|
||||||
|
v2 = call_indirect.i64 sig0, v1(v0)
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
; run: %call_ind_colocated(10) == 20
|
||||||
26
cranelift/filetests/filetests/runtests/call_libcall.clif
Normal file
26
cranelift/filetests/filetests/runtests/call_libcall.clif
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
test run
|
||||||
|
target x86_64
|
||||||
|
; AArch64 Does not have these libcalls
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
|
||||||
|
function %libcall_ceilf32(f32) -> f32 {
|
||||||
|
fn0 = %CeilF32(f32) -> f32
|
||||||
|
|
||||||
|
block0(v0: f32):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %libcall_ceilf32(0x0.5) == 0x1.0
|
||||||
|
|
||||||
|
|
||||||
|
function %libcall_indirect_ceilf32(f32) -> f32 {
|
||||||
|
fn0 = %CeilF32(f32) -> f32
|
||||||
|
; sig0 = (f32) -> f32
|
||||||
|
|
||||||
|
block0(v0: f32):
|
||||||
|
v1 = func_addr.i64 fn0
|
||||||
|
v2 = call_indirect.i64 sig0, v1(v0)
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
; run: %libcall_indirect_ceilf32(0x0.5) == 0x1.0
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
test interpret
|
test interpret
|
||||||
|
test run
|
||||||
|
target x86_64
|
||||||
|
target aarch64
|
||||||
|
target aarch64 sign_return_address
|
||||||
|
target aarch64 has_pauth sign_return_address
|
||||||
|
target s390x
|
||||||
|
|
||||||
; A non-recursive fibonacci implementation.
|
; A non-recursive fibonacci implementation.
|
||||||
function %fibonacci(i32) -> i32 {
|
function %fibonacci(i32) -> i32 {
|
||||||
|
|||||||
@@ -232,17 +232,3 @@ block2:
|
|||||||
; run: %i128_bricmp_uge(0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFD, 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF) == false
|
; run: %i128_bricmp_uge(0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFD, 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF) == false
|
||||||
; run: %i128_bricmp_uge(0xC0FFEEEE_C0FFEEEE_00000000_00000000, 0xDECAFFFF_DECAFFFF_00000000_00000000) == false
|
; run: %i128_bricmp_uge(0xC0FFEEEE_C0FFEEEE_00000000_00000000, 0xDECAFFFF_DECAFFFF_00000000_00000000) == false
|
||||||
; run: %i128_bricmp_uge(0xDECAFFFF_DECAFFFF_00000000_00000000, 0xC0FFEEEE_C0FFEEEE_00000000_00000000) == true
|
; run: %i128_bricmp_uge(0xDECAFFFF_DECAFFFF_00000000_00000000, 0xC0FFEEEE_C0FFEEEE_00000000_00000000) == true
|
||||||
|
|
||||||
function %i128_bricmp_of(i128, i128) -> b1 {
|
|
||||||
block0(v0: i128,v1: i128):
|
|
||||||
br_icmp.i128 of v0, v1, block2
|
|
||||||
jump block1
|
|
||||||
|
|
||||||
block1:
|
|
||||||
v2 = bconst.b1 false
|
|
||||||
return v2
|
|
||||||
|
|
||||||
block2:
|
|
||||||
v3 = bconst.b1 true
|
|
||||||
return v3
|
|
||||||
}
|
|
||||||
|
|||||||
23
cranelift/filetests/filetests/runtests/i128-call.clif
Normal file
23
cranelift/filetests/filetests/runtests/i128-call.clif
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
test run
|
||||||
|
set enable_llvm_abi_extensions=true
|
||||||
|
target x86_64
|
||||||
|
target aarch64
|
||||||
|
target aarch64 sign_return_address
|
||||||
|
target aarch64 has_pauth sign_return_address
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
|
||||||
|
function %callee_i128(i128) -> i128 {
|
||||||
|
block0(v0: i128):
|
||||||
|
v1 = iadd_imm.i128 v0, 10
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
function %call_i128(i128) -> i128 {
|
||||||
|
fn0 = %callee_i128(i128) -> i128
|
||||||
|
|
||||||
|
block0(v0: i128):
|
||||||
|
v1 = call fn0(v0)
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
; run: %call_i128(10) == 20
|
||||||
@@ -22,7 +22,7 @@ block0(v0: i8x16, v1: i8x16, v2: i8x16):
|
|||||||
; Remember that bitselect accepts: 1) the selector vector, 2) the "if true" vector, and 3) the "if false" vector.
|
; Remember that bitselect accepts: 1) the selector vector, 2) the "if true" vector, and 3) the "if false" vector.
|
||||||
; run: %bitselect_i8x16([0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255], [127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42], [42 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127]) == [42 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42]
|
; run: %bitselect_i8x16([0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255], [127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42], [42 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127]) == [42 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42]
|
||||||
|
|
||||||
function %bitselect_i8x16() -> b1 {
|
function %bitselect_i8x16_1() -> b1 {
|
||||||
block0:
|
block0:
|
||||||
v0 = vconst.i8x16 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255] ; the selector vector
|
v0 = vconst.i8x16 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255] ; the selector vector
|
||||||
v1 = vconst.i8x16 [127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42] ; for each 1-bit in v0 the bit of v1 is selected
|
v1 = vconst.i8x16 [127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42] ; for each 1-bit in v0 the bit of v1 is selected
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ block0:
|
|||||||
}
|
}
|
||||||
; run: %vselect_i16x8() == [200 101 202 103 204 105 106 107]
|
; run: %vselect_i16x8() == [200 101 202 103 204 105 106 107]
|
||||||
|
|
||||||
function %vselect_i32x4() -> i32x4 {
|
function %vselect_i32x4_const() -> i32x4 {
|
||||||
block0:
|
block0:
|
||||||
v1 = vconst.b32x4 [false true false true]
|
v1 = vconst.b32x4 [false true false true]
|
||||||
v2 = vconst.i32x4 [100 101 102 103]
|
v2 = vconst.i32x4 [100 101 102 103]
|
||||||
@@ -33,7 +33,15 @@ block0:
|
|||||||
v4 = vselect v1, v2, v3
|
v4 = vselect v1, v2, v3
|
||||||
return v4
|
return v4
|
||||||
}
|
}
|
||||||
; run: %vselect_i32x4() == [200 101 202 103]
|
; run: %vselect_i32x4_const() == [200 101 202 103]
|
||||||
|
|
||||||
|
function %vselect_i32x4(b32x4, i32x4, i32x4) -> i32x4 {
|
||||||
|
block0(v0: b32x4, v1: i32x4, v2: i32x4):
|
||||||
|
v3 = vselect v0, v1, v2
|
||||||
|
return v3
|
||||||
|
}
|
||||||
|
; Remember that vselect accepts: 1) the selector vector, 2) the "if true" vector, and 3) the "if false" vector.
|
||||||
|
; run: %vselect_i32x4([true true false false], [1 2 -1 -1], [-1 -1 3 4]) == [1 2 3 4]
|
||||||
|
|
||||||
function %vselect_i64x2() -> i64x2 {
|
function %vselect_i64x2() -> i64x2 {
|
||||||
block0:
|
block0:
|
||||||
@@ -72,15 +80,3 @@ block0(v0: b64x2, v1: i64x2, v2: i64x2):
|
|||||||
return v3
|
return v3
|
||||||
}
|
}
|
||||||
; run: %vselect_p_i64x2([true false], [1 2], [100000000000 200000000000]) == [1 200000000000]
|
; run: %vselect_p_i64x2([true false], [1 2], [100000000000 200000000000]) == [1 200000000000]
|
||||||
|
|
||||||
|
|
||||||
function %vselect_i32x4(i32x4, i32x4) -> i32x4 {
|
|
||||||
block0(v1: i32x4, v2: i32x4):
|
|
||||||
; `make_trampoline` still does not know how to convert boolean vector types
|
|
||||||
; so we load the value directly here.
|
|
||||||
v0 = vconst.b32x4 [true true false false]
|
|
||||||
v3 = vselect v0, v1, v2
|
|
||||||
return v3
|
|
||||||
}
|
|
||||||
; Remember that vselect accepts: 1) the selector vector, 2) the "if true" vector, and 3) the "if false" vector.
|
|
||||||
; run: %vselect_i32x4([1 2 -1 -1], [-1 -1 3 4]) == [1 2 3 4]
|
|
||||||
|
|||||||
@@ -1,52 +1,106 @@
|
|||||||
//! Provides functionality for compiling and running CLIF IR for `run` tests.
|
//! Provides functionality for compiling and running CLIF IR for `run` tests.
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::ir::{condcodes::IntCC, Function, InstBuilder, Signature};
|
use cranelift_codegen::ir::{
|
||||||
|
condcodes::IntCC, ExternalName, Function, InstBuilder, Signature, UserExternalName,
|
||||||
|
UserFuncName,
|
||||||
|
};
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_codegen::{ir, settings, CodegenError};
|
use cranelift_codegen::{ir, settings, CodegenError, Context};
|
||||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||||
use cranelift_jit::{JITBuilder, JITModule};
|
use cranelift_jit::{JITBuilder, JITModule};
|
||||||
use cranelift_module::{FuncId, Linkage, Module, ModuleError};
|
use cranelift_module::{FuncId, Linkage, Module, ModuleError};
|
||||||
use cranelift_native::builder_with_options;
|
use cranelift_native::builder_with_options;
|
||||||
|
use cranelift_reader::TestFile;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// Compile a single function.
|
const TESTFILE_NAMESPACE: u32 = 0;
|
||||||
|
|
||||||
|
/// Holds information about a previously defined function.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DefinedFunction {
|
||||||
|
/// This is the name that the function is internally known as.
|
||||||
|
///
|
||||||
|
/// The JIT module does not support linking / calling [TestcaseName]'s, so
|
||||||
|
/// we rename every function into a [UserExternalName].
|
||||||
|
///
|
||||||
|
/// By doing this we also have to rename functions that previously were using a
|
||||||
|
/// [UserFuncName], since they may now be in conflict after the renaming that
|
||||||
|
/// occurred.
|
||||||
|
new_name: UserExternalName,
|
||||||
|
|
||||||
|
/// The function signature
|
||||||
|
signature: ir::Signature,
|
||||||
|
|
||||||
|
/// JIT [FuncId]
|
||||||
|
func_id: FuncId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile a test case.
|
||||||
///
|
///
|
||||||
/// Several Cranelift functions need the ability to run Cranelift IR (e.g. `test_run`); this
|
/// Several Cranelift functions need the ability to run Cranelift IR (e.g. `test_run`); this
|
||||||
/// [SingleFunctionCompiler] provides a way for compiling Cranelift [Function]s to
|
/// [TestFileCompiler] provides a way for compiling Cranelift [Function]s to
|
||||||
/// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its
|
/// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its
|
||||||
/// name indicates, this compiler is limited: any functionality that requires knowledge of things
|
/// name indicates, this compiler is limited: any functionality that requires knowledge of things
|
||||||
/// outside the [Function] will likely not work (e.g. global values, calls). For an example of this
|
/// outside the [Function] will likely not work (e.g. global values, calls). For an example of this
|
||||||
/// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`.
|
/// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use cranelift_filetests::SingleFunctionCompiler;
|
/// use cranelift_filetests::TestFileCompiler;
|
||||||
/// use cranelift_reader::parse_functions;
|
/// use cranelift_reader::parse_functions;
|
||||||
/// use cranelift_codegen::data_value::DataValue;
|
/// use cranelift_codegen::data_value::DataValue;
|
||||||
///
|
///
|
||||||
/// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into();
|
/// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into();
|
||||||
/// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap();
|
/// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap();
|
||||||
/// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
|
/// let mut compiler = TestFileCompiler::with_default_host_isa().unwrap();
|
||||||
/// let compiled_func = compiler.compile(func).unwrap();
|
/// compiler.declare_function(&func).unwrap();
|
||||||
|
/// compiler.define_function(func.clone()).unwrap();
|
||||||
|
/// compiler.create_trampoline_for_function(&func).unwrap();
|
||||||
|
/// let compiled = compiler.compile().unwrap();
|
||||||
|
/// let trampoline = compiled.get_trampoline(&func).unwrap();
|
||||||
///
|
///
|
||||||
/// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]);
|
/// let returned = trampoline.call(&vec![DataValue::I32(2), DataValue::I32(40)]);
|
||||||
/// assert_eq!(vec![DataValue::I32(42)], returned);
|
/// assert_eq!(vec![DataValue::I32(42)], returned);
|
||||||
/// ```
|
/// ```
|
||||||
pub struct SingleFunctionCompiler {
|
pub struct TestFileCompiler {
|
||||||
isa: Box<dyn TargetIsa>,
|
module: JITModule,
|
||||||
|
ctx: Context,
|
||||||
|
|
||||||
|
/// Holds info about the functions that have already been defined.
|
||||||
|
/// Use look them up by their original [UserFuncName] since that's how the caller
|
||||||
|
/// passes them to us.
|
||||||
|
defined_functions: HashMap<UserFuncName, DefinedFunction>,
|
||||||
|
|
||||||
|
/// We deduplicate trampolines by the signature of the function that they target.
|
||||||
|
/// This map holds as a key the [Signature] of the target function, and as a value
|
||||||
|
/// the [UserFuncName] of the trampoline for that [Signature].
|
||||||
|
///
|
||||||
|
/// The trampoline is defined in `defined_functions` as any other regular function.
|
||||||
|
trampolines: HashMap<Signature, UserFuncName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SingleFunctionCompiler {
|
impl TestFileCompiler {
|
||||||
/// Build a [SingleFunctionCompiler] from a [TargetIsa]. For functions to be runnable on the
|
/// Build a [TestFileCompiler] from a [TargetIsa]. For functions to be runnable on the
|
||||||
/// host machine, this [TargetIsa] must match the host machine's ISA (see
|
/// host machine, this [TargetIsa] must match the host machine's ISA (see
|
||||||
/// [SingleFunctionCompiler::with_host_isa]).
|
/// [TestFileCompiler::with_host_isa]).
|
||||||
pub fn new(isa: Box<dyn TargetIsa>) -> Self {
|
pub fn new(isa: Box<dyn TargetIsa>) -> Self {
|
||||||
Self { isa }
|
let builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
|
||||||
|
let module = JITModule::new(builder);
|
||||||
|
let ctx = module.make_context();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
module,
|
||||||
|
ctx,
|
||||||
|
defined_functions: HashMap::new(),
|
||||||
|
trampolines: HashMap::new(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a [SingleFunctionCompiler] using the host machine's ISA and the passed flags.
|
/// Build a [TestFileCompiler] using the host machine's ISA and the passed flags.
|
||||||
pub fn with_host_isa(flags: settings::Flags) -> Result<Self> {
|
pub fn with_host_isa(flags: settings::Flags) -> Result<Self> {
|
||||||
let builder =
|
let builder =
|
||||||
builder_with_options(true).expect("Unable to build a TargetIsa for the current host");
|
builder_with_options(true).expect("Unable to build a TargetIsa for the current host");
|
||||||
@@ -54,59 +108,218 @@ impl SingleFunctionCompiler {
|
|||||||
Ok(Self::new(isa))
|
Ok(Self::new(isa))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a [SingleFunctionCompiler] using the host machine's ISA and the default flags for this
|
/// Build a [TestFileCompiler] using the host machine's ISA and the default flags for this
|
||||||
/// ISA.
|
/// ISA.
|
||||||
pub fn with_default_host_isa() -> Result<Self> {
|
pub fn with_default_host_isa() -> Result<Self> {
|
||||||
let flags = settings::Flags::new(settings::builder());
|
let flags = settings::Flags::new(settings::builder());
|
||||||
Self::with_host_isa(flags)
|
Self::with_host_isa(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile the passed [Function] to a `CompiledFunction`. This function will:
|
/// Registers all functions in a [TestFile]. Additionally creates a trampoline for each one
|
||||||
/// - check that the default ISA calling convention is used (to ensure it can be called)
|
/// of them.
|
||||||
/// - compile the [Function]
|
pub fn add_testfile(&mut self, testfile: &TestFile) -> Result<()> {
|
||||||
/// - compile a `Trampoline` for the [Function]'s signature (or used a cached `Trampoline`;
|
// Declare all functions in the file, so that they may refer to each other.
|
||||||
/// this makes it possible to call functions when the signature is not known until runtime.
|
for (func, _) in &testfile.functions {
|
||||||
pub fn compile(self, function: Function) -> Result<CompiledFunction, CompilationError> {
|
self.declare_function(func)?;
|
||||||
let signature = function.signature.clone();
|
|
||||||
if signature.call_conv != self.isa.default_call_conv() {
|
|
||||||
return Err(CompilationError::InvalidTargetIsa);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let trampoline = make_trampoline(&signature, self.isa.as_ref());
|
// Define all functions and trampolines
|
||||||
|
for (func, _) in &testfile.functions {
|
||||||
|
self.define_function(func.clone())?;
|
||||||
|
self.create_trampoline_for_function(func)?;
|
||||||
|
}
|
||||||
|
|
||||||
let builder = JITBuilder::with_isa(self.isa, cranelift_module::default_libcall_names());
|
Ok(())
|
||||||
let mut module = JITModule::new(builder);
|
}
|
||||||
let mut ctx = module.make_context();
|
|
||||||
|
|
||||||
let name = function.name.to_string();
|
/// Declares a function an registers it as a linkable and callable target internally
|
||||||
let func_id = module.declare_function(&name, Linkage::Local, &function.signature)?;
|
pub fn declare_function(&mut self, func: &Function) -> Result<()> {
|
||||||
|
let next_id = self.defined_functions.len() as u32;
|
||||||
|
match self.defined_functions.entry(func.name.clone()) {
|
||||||
|
Entry::Occupied(_) => {
|
||||||
|
anyhow::bail!("Duplicate function with name {} found!", &func.name)
|
||||||
|
}
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
let name = func.name.to_string();
|
||||||
|
let func_id =
|
||||||
|
self.module
|
||||||
|
.declare_function(&name, Linkage::Local, &func.signature)?;
|
||||||
|
|
||||||
// Build and declare the trampoline in the module
|
v.insert(DefinedFunction {
|
||||||
let trampoline_name = trampoline.name.to_string();
|
new_name: UserExternalName::new(TESTFILE_NAMESPACE, next_id),
|
||||||
let trampoline_id =
|
signature: func.signature.clone(),
|
||||||
module.declare_function(&trampoline_name, Linkage::Local, &trampoline.signature)?;
|
func_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Define both functions
|
Ok(())
|
||||||
let func_signature = function.signature.clone();
|
}
|
||||||
ctx.func = function;
|
|
||||||
module.define_function(func_id, &mut ctx)?;
|
|
||||||
module.clear_context(&mut ctx);
|
|
||||||
|
|
||||||
ctx.func = trampoline;
|
/// Renames the function to its new [UserExternalName], as well as any other function that
|
||||||
module.define_function(trampoline_id, &mut ctx)?;
|
/// it may reference.
|
||||||
module.clear_context(&mut ctx);
|
///
|
||||||
|
/// We have to do this since the JIT cannot link Testcase functions.
|
||||||
|
fn apply_func_rename(
|
||||||
|
&self,
|
||||||
|
mut func: Function,
|
||||||
|
defined_func: &DefinedFunction,
|
||||||
|
) -> Result<Function> {
|
||||||
|
// First, rename the function
|
||||||
|
let func_original_name = func.name;
|
||||||
|
func.name = UserFuncName::User(defined_func.new_name.clone());
|
||||||
|
|
||||||
|
// Rename any functions that it references
|
||||||
|
// Do this in stages to appease the borrow checker
|
||||||
|
let mut redefines = Vec::with_capacity(func.dfg.ext_funcs.len());
|
||||||
|
for (ext_ref, ext_func) in &func.dfg.ext_funcs {
|
||||||
|
let old_name = match &ext_func.name {
|
||||||
|
ExternalName::TestCase(tc) => UserFuncName::Testcase(tc.clone()),
|
||||||
|
ExternalName::User(username) => {
|
||||||
|
UserFuncName::User(func.params.user_named_funcs()[*username].clone())
|
||||||
|
}
|
||||||
|
// The other cases don't need renaming, so lets just continue...
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_df = self.defined_functions.get(&old_name).ok_or(anyhow!(
|
||||||
|
"Undeclared function {} is referenced by {}!",
|
||||||
|
&old_name,
|
||||||
|
&func_original_name
|
||||||
|
))?;
|
||||||
|
|
||||||
|
redefines.push((ext_ref, target_df.new_name.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now register the redefines
|
||||||
|
for (ext_ref, new_name) in redefines.into_iter() {
|
||||||
|
// Register the new name in the func, so that we can get a reference to it.
|
||||||
|
let new_name_ref = func.params.ensure_user_func_name(new_name);
|
||||||
|
|
||||||
|
// Finally rename the ExtFunc
|
||||||
|
func.dfg.ext_funcs[ext_ref].name = ExternalName::User(new_name_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(func)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines the body of a function
|
||||||
|
pub fn define_function(&mut self, func: Function) -> Result<()> {
|
||||||
|
let defined_func = self
|
||||||
|
.defined_functions
|
||||||
|
.get(&func.name)
|
||||||
|
.ok_or(anyhow!("Undeclared function {} found!", &func.name))?;
|
||||||
|
|
||||||
|
self.ctx.func = self.apply_func_rename(func, defined_func)?;
|
||||||
|
self.module
|
||||||
|
.define_function(defined_func.func_id, &mut self.ctx)?;
|
||||||
|
self.module.clear_context(&mut self.ctx);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and registers a trampoline for a function if none exists.
|
||||||
|
pub fn create_trampoline_for_function(&mut self, func: &Function) -> Result<()> {
|
||||||
|
if !self.defined_functions.contains_key(&func.name) {
|
||||||
|
anyhow::bail!("Undeclared function {} found!", &func.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a trampoline for this function signature already exists
|
||||||
|
if self.trampolines.contains_key(&func.signature) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a trampoline and register it
|
||||||
|
let name = UserFuncName::user(TESTFILE_NAMESPACE, self.defined_functions.len() as u32);
|
||||||
|
let trampoline = make_trampoline(name.clone(), &func.signature, self.module.isa());
|
||||||
|
|
||||||
|
self.declare_function(&trampoline)?;
|
||||||
|
self.define_function(trampoline)?;
|
||||||
|
|
||||||
|
self.trampolines.insert(func.signature.clone(), name);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finalize this TestFile and link all functions.
|
||||||
|
pub fn compile(mut self) -> Result<CompiledTestFile, CompilationError> {
|
||||||
// Finalize the functions which we just defined, which resolves any
|
// Finalize the functions which we just defined, which resolves any
|
||||||
// outstanding relocations (patching in addresses, now that they're
|
// outstanding relocations (patching in addresses, now that they're
|
||||||
// available).
|
// available).
|
||||||
module.finalize_definitions();
|
self.module.finalize_definitions();
|
||||||
|
|
||||||
Ok(CompiledFunction::new(
|
Ok(CompiledTestFile {
|
||||||
module,
|
module: Some(self.module),
|
||||||
func_signature,
|
defined_functions: self.defined_functions,
|
||||||
func_id,
|
trampolines: self.trampolines,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A finalized Test File
|
||||||
|
pub struct CompiledTestFile {
|
||||||
|
/// We need to store [JITModule] since it contains the underlying memory for the functions.
|
||||||
|
/// Store it in an [Option] so that we can later drop it.
|
||||||
|
module: Option<JITModule>,
|
||||||
|
|
||||||
|
/// Holds info about the functions that have been registered in `module`.
|
||||||
|
/// See [TestFileCompiler] for more info.
|
||||||
|
defined_functions: HashMap<UserFuncName, DefinedFunction>,
|
||||||
|
|
||||||
|
/// Trampolines available in this [JITModule].
|
||||||
|
/// See [TestFileCompiler] for more info.
|
||||||
|
trampolines: HashMap<Signature, UserFuncName>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompiledTestFile {
|
||||||
|
/// Return a trampoline for calling.
|
||||||
|
///
|
||||||
|
/// Returns None if [TestFileCompiler::create_trampoline_for_function] wasn't called for this function.
|
||||||
|
pub fn get_trampoline(&self, func: &Function) -> Option<Trampoline> {
|
||||||
|
let defined_func = self.defined_functions.get(&func.name)?;
|
||||||
|
let trampoline_id = self
|
||||||
|
.trampolines
|
||||||
|
.get(&func.signature)
|
||||||
|
.and_then(|name| self.defined_functions.get(name))
|
||||||
|
.map(|df| df.func_id)?;
|
||||||
|
Some(Trampoline {
|
||||||
|
module: self.module.as_ref()?,
|
||||||
|
func_id: defined_func.func_id,
|
||||||
|
func_signature: &defined_func.signature,
|
||||||
trampoline_id,
|
trampoline_id,
|
||||||
))
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CompiledTestFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Freeing the module's memory erases the compiled functions.
|
||||||
|
// This should be safe since their pointers never leave this struct.
|
||||||
|
unsafe { self.module.take().unwrap().free_memory() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A callable trampoline
|
||||||
|
pub struct Trampoline<'a> {
|
||||||
|
module: &'a JITModule,
|
||||||
|
func_id: FuncId,
|
||||||
|
func_signature: &'a Signature,
|
||||||
|
trampoline_id: FuncId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Trampoline<'a> {
|
||||||
|
/// Call the target function of this trampoline, passing in [DataValue]s using a compiled trampoline.
|
||||||
|
pub fn call(&self, arguments: &[DataValue]) -> Vec<DataValue> {
|
||||||
|
let mut values = UnboxedValues::make_arguments(arguments, &self.func_signature);
|
||||||
|
let arguments_address = values.as_mut_ptr();
|
||||||
|
|
||||||
|
let function_ptr = self.module.get_finalized_function(self.func_id);
|
||||||
|
let trampoline_ptr = self.module.get_finalized_function(self.trampoline_id);
|
||||||
|
|
||||||
|
let callable_trampoline: fn(*const u8, *mut u128) -> () =
|
||||||
|
unsafe { mem::transmute(trampoline_ptr) };
|
||||||
|
callable_trampoline(function_ptr, arguments_address);
|
||||||
|
|
||||||
|
values.collect_returns(&self.func_signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,72 +341,6 @@ pub enum CompilationError {
|
|||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Container for the compiled code of a [Function]. This wrapper allows users to call the compiled
|
|
||||||
/// function through the use of a trampoline.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use cranelift_filetests::SingleFunctionCompiler;
|
|
||||||
/// use cranelift_reader::parse_functions;
|
|
||||||
/// use cranelift_codegen::data_value::DataValue;
|
|
||||||
///
|
|
||||||
/// let code = "test run \n function %add(i32, i32) -> i32 { block0(v0:i32, v1:i32): v2 = iadd v0, v1 return v2 }".into();
|
|
||||||
/// let func = parse_functions(code).unwrap().into_iter().nth(0).unwrap();
|
|
||||||
/// let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
|
|
||||||
/// let compiled_func = compiler.compile(func).unwrap();
|
|
||||||
///
|
|
||||||
/// let returned = compiled_func.call(&vec![DataValue::I32(2), DataValue::I32(40)]);
|
|
||||||
/// assert_eq!(vec![DataValue::I32(42)], returned);
|
|
||||||
/// ```
|
|
||||||
pub struct CompiledFunction {
|
|
||||||
/// We need to store this since it contains the underlying memory for the functions
|
|
||||||
/// Store it in an [Option] so that we can later drop it.
|
|
||||||
module: Option<JITModule>,
|
|
||||||
signature: Signature,
|
|
||||||
func_id: FuncId,
|
|
||||||
trampoline_id: FuncId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompiledFunction {
|
|
||||||
/// Build a new [CompiledFunction].
|
|
||||||
pub fn new(
|
|
||||||
module: JITModule,
|
|
||||||
signature: Signature,
|
|
||||||
func_id: FuncId,
|
|
||||||
trampoline_id: FuncId,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
module: Some(module),
|
|
||||||
signature,
|
|
||||||
func_id,
|
|
||||||
trampoline_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the [CompiledFunction], passing in [DataValue]s using a compiled trampoline.
|
|
||||||
pub fn call(&self, arguments: &[DataValue]) -> Vec<DataValue> {
|
|
||||||
let mut values = UnboxedValues::make_arguments(arguments, &self.signature);
|
|
||||||
let arguments_address = values.as_mut_ptr();
|
|
||||||
|
|
||||||
let module = self.module.as_ref().unwrap();
|
|
||||||
let function_ptr = module.get_finalized_function(self.func_id);
|
|
||||||
let trampoline_ptr = module.get_finalized_function(self.trampoline_id);
|
|
||||||
|
|
||||||
let callable_trampoline: fn(*const u8, *mut u128) -> () =
|
|
||||||
unsafe { mem::transmute(trampoline_ptr) };
|
|
||||||
callable_trampoline(function_ptr, arguments_address);
|
|
||||||
|
|
||||||
values.collect_returns(&self.signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for CompiledFunction {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Freeing the module's memory erases the compiled functions.
|
|
||||||
// This should be safe since their pointers never leave this struct.
|
|
||||||
unsafe { self.module.take().unwrap().free_memory() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can
|
/// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can
|
||||||
/// understand.
|
/// understand.
|
||||||
struct UnboxedValues(Vec<u128>);
|
struct UnboxedValues(Vec<u128>);
|
||||||
@@ -252,15 +399,15 @@ impl UnboxedValues {
|
|||||||
/// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by
|
/// (e.g. register, stack) prior to calling a [CompiledFunction]. The [Function] returned by
|
||||||
/// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default
|
/// [make_trampoline] is compiled to a [Trampoline]. Note that this uses the [TargetIsa]'s default
|
||||||
/// calling convention so we must also check that the [CompiledFunction] has the same calling
|
/// calling convention so we must also check that the [CompiledFunction] has the same calling
|
||||||
/// convention (see [SingleFunctionCompiler::compile]).
|
/// convention (see [TestFileCompiler::compile]).
|
||||||
fn make_trampoline(signature: &ir::Signature, isa: &dyn TargetIsa) -> Function {
|
fn make_trampoline(name: UserFuncName, signature: &ir::Signature, isa: &dyn TargetIsa) -> Function {
|
||||||
// Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> ()
|
// Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> ()
|
||||||
let pointer_type = isa.pointer_type();
|
let pointer_type = isa.pointer_type();
|
||||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter.
|
wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `callee_address` parameter.
|
||||||
wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter.
|
wrapper_sig.params.push(ir::AbiParam::new(pointer_type)); // Add the `values_vec` parameter.
|
||||||
|
|
||||||
let mut func = ir::Function::with_name_signature(ir::UserFuncName::default(), wrapper_sig);
|
let mut func = ir::Function::with_name_signature(name, wrapper_sig);
|
||||||
|
|
||||||
// The trampoline has a single block filled with loads, one call to callee_address, and some loads.
|
// The trampoline has a single block filled with loads, one call to callee_address, and some loads.
|
||||||
let mut builder_context = FunctionBuilderContext::new();
|
let mut builder_context = FunctionBuilderContext::new();
|
||||||
@@ -385,9 +532,13 @@ mod test {
|
|||||||
let function = test_file.functions[0].0.clone();
|
let function = test_file.functions[0].0.clone();
|
||||||
|
|
||||||
// execute function
|
// execute function
|
||||||
let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
|
let mut compiler = TestFileCompiler::with_default_host_isa().unwrap();
|
||||||
let compiled_function = compiler.compile(function).unwrap();
|
compiler.declare_function(&function).unwrap();
|
||||||
let returned = compiled_function.call(&[]);
|
compiler.define_function(function.clone()).unwrap();
|
||||||
|
compiler.create_trampoline_for_function(&function).unwrap();
|
||||||
|
let compiled = compiler.compile().unwrap();
|
||||||
|
let trampoline = compiled.get_trampoline(&function).unwrap();
|
||||||
|
let returned = trampoline.call(&[]);
|
||||||
assert_eq!(returned, vec![DataValue::B(true)])
|
assert_eq!(returned, vec![DataValue::B(true)])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,8 +554,12 @@ mod test {
|
|||||||
}",
|
}",
|
||||||
);
|
);
|
||||||
|
|
||||||
let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap();
|
let compiler = TestFileCompiler::with_default_host_isa().unwrap();
|
||||||
let trampoline = make_trampoline(&function.signature, compiler.isa.as_ref());
|
let trampoline = make_trampoline(
|
||||||
|
UserFuncName::user(0, 0),
|
||||||
|
&function.signature,
|
||||||
|
compiler.module.isa(),
|
||||||
|
);
|
||||||
assert!(format!("{}", trampoline).ends_with(
|
assert!(format!("{}", trampoline).ends_with(
|
||||||
"sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast
|
"sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub use crate::function_runner::SingleFunctionCompiler;
|
pub use crate::function_runner::TestFileCompiler;
|
||||||
use crate::runner::TestRunner;
|
use crate::runner::TestRunner;
|
||||||
use cranelift_codegen::timing;
|
use cranelift_codegen::timing;
|
||||||
use cranelift_reader::TestCommand;
|
use cranelift_reader::TestCommand;
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
//! Run the tests in a single test file.
|
//! Run the tests in a single test file.
|
||||||
|
|
||||||
use crate::new_subtest;
|
use crate::new_subtest;
|
||||||
use crate::subtest::{Context, SubTest};
|
use crate::subtest::SubTest;
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use cranelift_codegen::ir::Function;
|
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_codegen::print_errors::pretty_verifier_error;
|
use cranelift_codegen::print_errors::pretty_verifier_error;
|
||||||
use cranelift_codegen::settings::Flags;
|
use cranelift_codegen::settings::{Flags, FlagsOrIsa};
|
||||||
use cranelift_codegen::timing;
|
use cranelift_codegen::timing;
|
||||||
use cranelift_codegen::verify_function;
|
use cranelift_codegen::verify_function;
|
||||||
use cranelift_reader::{parse_test, IsaSpec, Location, ParseOptions};
|
use cranelift_reader::{parse_test, IsaSpec, Location, ParseOptions, TestFile};
|
||||||
use log::info;
|
use log::info;
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -78,39 +76,38 @@ pub fn run(
|
|||||||
tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));
|
tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier()));
|
||||||
|
|
||||||
// Expand the tests into (test, flags, isa) tuples.
|
// Expand the tests into (test, flags, isa) tuples.
|
||||||
let mut tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;
|
let tuples = test_tuples(&tests, &testfile.isa_spec, flags)?;
|
||||||
|
|
||||||
// Isolate the last test in the hope that this is the only mutating test.
|
// Bail if the test has no runnable commands
|
||||||
// If so, we can completely avoid cloning functions.
|
if tuples.is_empty() {
|
||||||
let last_tuple = match tuples.pop() {
|
anyhow::bail!("no test commands found");
|
||||||
None => anyhow::bail!("no test commands found"),
|
}
|
||||||
Some(t) => t,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut file_update = FileUpdate::new(&path);
|
let mut file_update = FileUpdate::new(&path);
|
||||||
let file_path = path.to_string_lossy();
|
let file_path = path.to_string_lossy();
|
||||||
for (func, details) in testfile.functions {
|
for (test, flags, isa) in &tuples {
|
||||||
let mut context = Context {
|
// Should we run the verifier before this test?
|
||||||
preamble_comments: &testfile.preamble_comments,
|
if test.needs_verifier() {
|
||||||
details,
|
let fisa = FlagsOrIsa { flags, isa: *isa };
|
||||||
verified: false,
|
verify_testfile(&testfile, fisa)?;
|
||||||
flags,
|
|
||||||
isa: None,
|
|
||||||
file_path: file_path.as_ref(),
|
|
||||||
file_update: &mut file_update,
|
|
||||||
};
|
|
||||||
|
|
||||||
for tuple in &tuples {
|
|
||||||
run_one_test(*tuple, Cow::Borrowed(&func), &mut context)?;
|
|
||||||
}
|
}
|
||||||
// Run the last test with an owned function which means it won't need to clone it before
|
|
||||||
// mutating.
|
test.run_target(&testfile, &mut file_update, file_path.as_ref(), flags, *isa)?;
|
||||||
run_one_test(last_tuple, Cow::Owned(func), &mut context)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(started.elapsed())
|
Ok(started.elapsed())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies all functions in a testfile
|
||||||
|
fn verify_testfile(testfile: &TestFile, fisa: FlagsOrIsa) -> anyhow::Result<()> {
|
||||||
|
for (func, _) in &testfile.functions {
|
||||||
|
verify_function(func, fisa)
|
||||||
|
.map_err(|errors| anyhow::anyhow!("{}", pretty_verifier_error(&func, None, errors)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Given a slice of tests, generate a vector of (test, flags, isa) tuples.
|
// Given a slice of tests, generate a vector of (test, flags, isa) tuples.
|
||||||
fn test_tuples<'a>(
|
fn test_tuples<'a>(
|
||||||
tests: &'a [Box<dyn SubTest>],
|
tests: &'a [Box<dyn SubTest>],
|
||||||
@@ -141,29 +138,6 @@ fn test_tuples<'a>(
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_one_test<'a>(
|
|
||||||
tuple: (&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>),
|
|
||||||
func: Cow<Function>,
|
|
||||||
context: &mut Context<'a>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let (test, flags, isa) = tuple;
|
|
||||||
let name = format!("{}({})", test.name(), func.name);
|
|
||||||
info!("Test: {} {}", name, isa.map_or("-", TargetIsa::name));
|
|
||||||
|
|
||||||
context.flags = flags;
|
|
||||||
context.isa = isa;
|
|
||||||
|
|
||||||
// Should we run the verifier before this test?
|
|
||||||
if !context.verified && test.needs_verifier() {
|
|
||||||
verify_function(&func, context.flags_or_isa())
|
|
||||||
.map_err(|errors| anyhow::anyhow!("{}", pretty_verifier_error(&func, None, errors)))?;
|
|
||||||
context.verified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
test.run(func, context).context(test.name())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A helper struct to update a file in-place as test expectations are
|
/// A helper struct to update a file in-place as test expectations are
|
||||||
/// automatically updated.
|
/// automatically updated.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ use anyhow::Context as _;
|
|||||||
use cranelift_codegen::ir::Function;
|
use cranelift_codegen::ir::Function;
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_codegen::settings::{Flags, FlagsOrIsa};
|
use cranelift_codegen::settings::{Flags, FlagsOrIsa};
|
||||||
use cranelift_reader::{Comment, Details};
|
use cranelift_reader::{Comment, Details, TestFile};
|
||||||
use filecheck::{Checker, CheckerBuilder, NO_VARIABLES};
|
use filecheck::{Checker, CheckerBuilder, NO_VARIABLES};
|
||||||
|
use log::info;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Context for running a test on a single function.
|
/// Context for running a test on a single function.
|
||||||
@@ -15,10 +16,7 @@ pub struct Context<'a> {
|
|||||||
pub preamble_comments: &'a [Comment<'a>],
|
pub preamble_comments: &'a [Comment<'a>],
|
||||||
|
|
||||||
/// Additional details about the function from the parser.
|
/// Additional details about the function from the parser.
|
||||||
pub details: Details<'a>,
|
pub details: &'a Details<'a>,
|
||||||
|
|
||||||
/// Was the function verified before running this test?
|
|
||||||
pub verified: bool,
|
|
||||||
|
|
||||||
/// ISA-independent flags for this test.
|
/// ISA-independent flags for this test.
|
||||||
pub flags: &'a Flags,
|
pub flags: &'a Flags,
|
||||||
@@ -69,6 +67,40 @@ pub trait SubTest {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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<()> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.run(Cow::Borrowed(&func), &context)
|
||||||
|
.context(self.name())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Run this test on `func`.
|
/// Run this test on `func`.
|
||||||
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()>;
|
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
//!
|
//!
|
||||||
//! The `run` test command compiles each function on the host machine and executes it
|
//! The `run` test command compiles each function on the host machine and executes it
|
||||||
|
|
||||||
use crate::function_runner::SingleFunctionCompiler;
|
use crate::function_runner::{CompiledTestFile, TestFileCompiler};
|
||||||
|
use crate::runone::FileUpdate;
|
||||||
use crate::runtest_environment::{HeapMemory, RuntestEnvironment};
|
use crate::runtest_environment::{HeapMemory, RuntestEnvironment};
|
||||||
use crate::subtest::{Context, SubTest};
|
use crate::subtest::{Context, SubTest};
|
||||||
|
use anyhow::Context as _;
|
||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::ir::Type;
|
use cranelift_codegen::ir::Type;
|
||||||
use cranelift_codegen::isa::TargetIsa;
|
use cranelift_codegen::isa::TargetIsa;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::{Configurable, Flags};
|
||||||
use cranelift_codegen::{ir, settings};
|
use cranelift_codegen::{ir, settings};
|
||||||
use cranelift_reader::parse_run_command;
|
|
||||||
use cranelift_reader::TestCommand;
|
use cranelift_reader::TestCommand;
|
||||||
use log::trace;
|
use cranelift_reader::{parse_run_command, TestFile};
|
||||||
|
use log::{info, trace};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
struct TestRun;
|
struct TestRun;
|
||||||
@@ -59,7 +61,7 @@ fn build_host_isa(
|
|||||||
|
|
||||||
/// Checks if the host's ISA is compatible with the one requested by the test.
|
/// Checks if the host's ISA is compatible with the one requested by the test.
|
||||||
fn is_isa_compatible(
|
fn is_isa_compatible(
|
||||||
context: &Context,
|
file_path: &str,
|
||||||
host: &dyn TargetIsa,
|
host: &dyn TargetIsa,
|
||||||
requested: &dyn TargetIsa,
|
requested: &dyn TargetIsa,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
@@ -71,7 +73,7 @@ fn is_isa_compatible(
|
|||||||
if host_arch != requested_arch {
|
if host_arch != requested_arch {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"skipped {}: host can't run {:?} programs",
|
"skipped {}: host can't run {:?} programs",
|
||||||
context.file_path, requested_arch
|
file_path, requested_arch
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ fn is_isa_compatible(
|
|||||||
if requested && !available_in_host {
|
if requested && !available_in_host {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"skipped {}: host does not support ISA flag {}",
|
"skipped {}: host does not support ISA flag {}",
|
||||||
context.file_path, req_value.name
|
file_path, req_value.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -101,6 +103,54 @@ fn is_isa_compatible(
|
|||||||
Ok(())
|
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 {
|
impl SubTest for TestRun {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"run"
|
"run"
|
||||||
@@ -114,10 +164,19 @@ impl SubTest for TestRun {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
|
/// 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.
|
// Disable runtests with pinned reg enabled.
|
||||||
// We've had some abi issues that the trampoline isn't quite ready for.
|
// We've had some abi issues that the trampoline isn't quite ready for.
|
||||||
if context.flags.enable_pinned_reg() {
|
if flags.enable_pinned_reg() {
|
||||||
return Err(anyhow::anyhow!([
|
return Err(anyhow::anyhow!([
|
||||||
"Cannot run runtests with pinned_reg enabled.",
|
"Cannot run runtests with pinned_reg enabled.",
|
||||||
"See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info"
|
"See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info"
|
||||||
@@ -125,46 +184,41 @@ impl SubTest for TestRun {
|
|||||||
.join("\n")));
|
.join("\n")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_isa = build_host_isa(true, context.flags.clone(), vec![]);
|
// Check that the host machine can run this test case (i.e. has all extensions)
|
||||||
let requested_isa = context.isa.unwrap();
|
let host_isa = build_host_isa(true, flags.clone(), vec![]);
|
||||||
if let Err(e) = is_isa_compatible(context, host_isa.as_ref(), requested_isa) {
|
if let Err(e) = is_isa_compatible(file_path, host_isa.as_ref(), isa.unwrap()) {
|
||||||
log::info!("{}", e);
|
log::info!("{}", e);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let test_env = RuntestEnvironment::parse(&context.details.comments[..])?;
|
let compiled_testfile = compile_testfile(&testfile, flags, isa.unwrap())?;
|
||||||
|
|
||||||
for comment in context.details.comments.iter() {
|
for (func, details) in &testfile.functions {
|
||||||
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
|
info!(
|
||||||
trace!("Parsed run command: {}", command);
|
"Test: {}({}) {}",
|
||||||
|
self.name(),
|
||||||
|
func.name,
|
||||||
|
isa.map_or("-", TargetIsa::name)
|
||||||
|
);
|
||||||
|
|
||||||
// We can't use the requested ISA directly since it does not contain info
|
let context = Context {
|
||||||
// about the operating system / calling convention / etc..
|
preamble_comments: &testfile.preamble_comments,
|
||||||
//
|
details,
|
||||||
// Copy the requested ISA flags into the host ISA and use that.
|
flags,
|
||||||
let isa = build_host_isa(false, context.flags.clone(), requested_isa.isa_flags());
|
isa,
|
||||||
|
file_path: file_path.as_ref(),
|
||||||
|
file_update,
|
||||||
|
};
|
||||||
|
|
||||||
let compiled_fn =
|
run_test(&compiled_testfile, &func, &context).context(self.name())?;
|
||||||
SingleFunctionCompiler::new(isa).compile(func.clone().into_owned())?;
|
|
||||||
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);
|
|
||||||
|
|
||||||
Ok(compiled_fn.call(&args))
|
|
||||||
})
|
|
||||||
.map_err(|s| anyhow::anyhow!("{}", s))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
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.
|
/// Build a VMContext struct with the layout described in docs/testing.md.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::utils::{iterate_files, read_to_string};
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cranelift_codegen::isa::{CallConv, TargetIsa};
|
use cranelift_codegen::isa::{CallConv, TargetIsa};
|
||||||
use cranelift_filetests::SingleFunctionCompiler;
|
use cranelift_filetests::TestFileCompiler;
|
||||||
use cranelift_native::builder as host_isa_builder;
|
use cranelift_native::builder as host_isa_builder;
|
||||||
use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions};
|
use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -85,13 +85,18 @@ fn run_file_contents(file_contents: String) -> Result<()> {
|
|||||||
..ParseOptions::default()
|
..ParseOptions::default()
|
||||||
};
|
};
|
||||||
let test_file = parse_test(&file_contents, options)?;
|
let test_file = parse_test(&file_contents, options)?;
|
||||||
|
let isa = create_target_isa(&test_file.isa_spec)?;
|
||||||
|
let mut tfc = TestFileCompiler::new(isa);
|
||||||
|
tfc.add_testfile(&test_file)?;
|
||||||
|
let compiled = tfc.compile()?;
|
||||||
|
|
||||||
for (func, Details { comments, .. }) in test_file.functions {
|
for (func, Details { comments, .. }) in test_file.functions {
|
||||||
for comment in comments {
|
for comment in comments {
|
||||||
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
|
if let Some(command) = parse_run_command(comment.text, &func.signature)? {
|
||||||
let isa = create_target_isa(&test_file.isa_spec)?;
|
let trampoline = compiled.get_trampoline(&func).unwrap();
|
||||||
let compiled_fn = SingleFunctionCompiler::new(isa).compile(func.clone())?;
|
|
||||||
command
|
command
|
||||||
.run(|_, args| Ok(compiled_fn.call(args)))
|
.run(|_, args| Ok(trampoline.call(args)))
|
||||||
.map_err(|s| anyhow::anyhow!("{}", s))?;
|
.map_err(|s| anyhow::anyhow!("{}", s))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use libfuzzer_sys::fuzz_target;
|
|||||||
use cranelift_codegen::data_value::DataValue;
|
use cranelift_codegen::data_value::DataValue;
|
||||||
use cranelift_codegen::settings;
|
use cranelift_codegen::settings;
|
||||||
use cranelift_codegen::settings::Configurable;
|
use cranelift_codegen::settings::Configurable;
|
||||||
use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompiler};
|
use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline};
|
||||||
use cranelift_fuzzgen::*;
|
use cranelift_fuzzgen::*;
|
||||||
use cranelift_interpreter::environment::FuncIndex;
|
use cranelift_interpreter::environment::FuncIndex;
|
||||||
use cranelift_interpreter::environment::FunctionStore;
|
use cranelift_interpreter::environment::FunctionStore;
|
||||||
@@ -46,8 +46,8 @@ fn run_in_interpreter(interpreter: &mut Interpreter, args: &[DataValue]) -> RunR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_in_host(compiled_fn: &CompiledFunction, args: &[DataValue]) -> RunResult {
|
fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult {
|
||||||
let res = compiled_fn.call(args);
|
let res = trampoline.call(args);
|
||||||
RunResult::Success(res)
|
RunResult::Success(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,8 +68,14 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
builder.set("enable_llvm_abi_extensions", "true").unwrap();
|
builder.set("enable_llvm_abi_extensions", "true").unwrap();
|
||||||
settings::Flags::new(builder)
|
settings::Flags::new(builder)
|
||||||
};
|
};
|
||||||
let host_compiler = SingleFunctionCompiler::with_host_isa(flags).unwrap();
|
let mut compiler = TestFileCompiler::with_host_isa(flags).unwrap();
|
||||||
let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap();
|
compiler.declare_function(&testcase.func).unwrap();
|
||||||
|
compiler.define_function(testcase.func.clone()).unwrap();
|
||||||
|
compiler
|
||||||
|
.create_trampoline_for_function(&testcase.func)
|
||||||
|
.unwrap();
|
||||||
|
let compiled = compiler.compile().unwrap();
|
||||||
|
let trampoline = compiled.get_trampoline(&testcase.func).unwrap();
|
||||||
|
|
||||||
for args in &testcase.inputs {
|
for args in &testcase.inputs {
|
||||||
// We rebuild the interpreter every run so that we don't accidentally carry over any state
|
// We rebuild the interpreter every run so that we don't accidentally carry over any state
|
||||||
@@ -93,7 +99,7 @@ fuzz_target!(|testcase: TestCase| {
|
|||||||
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
RunResult::Error(_) => panic!("interpreter failed: {:?}", int_res),
|
||||||
}
|
}
|
||||||
|
|
||||||
let host_res = run_in_host(&compiled_fn, args);
|
let host_res = run_in_host(&trampoline, args);
|
||||||
match host_res {
|
match host_res {
|
||||||
RunResult::Success(_) => {}
|
RunResult::Success(_) => {}
|
||||||
_ => panic!("host failed: {:?}", host_res),
|
_ => panic!("host failed: {:?}", host_res),
|
||||||
|
|||||||
Reference in New Issue
Block a user