diff --git a/cranelift/codegen/src/ir/extname.rs b/cranelift/codegen/src/ir/extname.rs index 1501112a2dfc..00552bbd6949 100644 --- a/cranelift/codegen/src/ir/extname.rs +++ b/cranelift/codegen/src/ir/extname.rs @@ -21,7 +21,7 @@ use super::function::FunctionParameters; /// 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 /// relocations later, etc. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum UserFuncName { /// 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. 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, } +impl UserExternalName { + /// Creates a new [UserExternalName]. + pub fn new(namespace: u32, index: u32) -> Self { + Self { namespace, index } + } +} + impl fmt::Display for UserExternalName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "u{}:{}", self.namespace, self.index) diff --git a/cranelift/filetests/filetests/runtests/atomic-rmw-subword-big.clif b/cranelift/filetests/filetests/runtests/atomic-rmw-subword-big.clif index 8e24b8e55d5a..dacbcb708e92 100644 --- a/cranelift/filetests/filetests/runtests/atomic-rmw-subword-big.clif +++ b/cranelift/filetests/filetests/runtests/atomic-rmw-subword-big.clif @@ -18,10 +18,10 @@ block0(v0: i32, v1: i64, v2: i16): v6 = load.i32 big v3 return v6 } -; run: %atomic_rmw_add_little_i16(0x12345678, 0, 0x1111) == 0x23455678 -; run: %atomic_rmw_add_little_i16(0x12345678, 0, 0xffff) == 0x12335678 -; run: %atomic_rmw_add_little_i16(0x12345678, 2, 0x1111) == 0x12346789 -; run: %atomic_rmw_add_little_i16(0x12345678, 2, 0xffff) == 0x12345677 +; run: %atomic_rmw_add_big_i16(0x12345678, 0, 0x1111) == 0x23455678 +; run: %atomic_rmw_add_big_i16(0x12345678, 0, 0xffff) == 0x12335678 +; run: %atomic_rmw_add_big_i16(0x12345678, 2, 0x1111) == 0x12346789 +; run: %atomic_rmw_add_big_i16(0x12345678, 2, 0xffff) == 0x12345677 function %atomic_rmw_add_big_i8(i32, i64, i8) -> i32 { ss0 = explicit_slot 4 diff --git a/cranelift/filetests/filetests/runtests/call.clif b/cranelift/filetests/filetests/runtests/call.clif new file mode 100644 index 000000000000..deb59423fe3c --- /dev/null +++ b/cranelift/filetests/filetests/runtests/call.clif @@ -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 diff --git a/cranelift/filetests/filetests/runtests/call_indirect.clif b/cranelift/filetests/filetests/runtests/call_indirect.clif new file mode 100644 index 000000000000..3705001c9891 --- /dev/null +++ b/cranelift/filetests/filetests/runtests/call_indirect.clif @@ -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 diff --git a/cranelift/filetests/filetests/runtests/call_libcall.clif b/cranelift/filetests/filetests/runtests/call_libcall.clif new file mode 100644 index 000000000000..3e9cafd37bdc --- /dev/null +++ b/cranelift/filetests/filetests/runtests/call_libcall.clif @@ -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 diff --git a/cranelift/filetests/filetests/runtests/fibonacci.clif b/cranelift/filetests/filetests/runtests/fibonacci.clif index 487733b08b36..72e1f67e831b 100644 --- a/cranelift/filetests/filetests/runtests/fibonacci.clif +++ b/cranelift/filetests/filetests/runtests/fibonacci.clif @@ -1,4 +1,10 @@ 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. function %fibonacci(i32) -> i32 { diff --git a/cranelift/filetests/filetests/runtests/i128-bricmp.clif b/cranelift/filetests/filetests/runtests/i128-bricmp.clif index 6c367177cd33..0cf19c4196d9 100644 --- a/cranelift/filetests/filetests/runtests/i128-bricmp.clif +++ b/cranelift/filetests/filetests/runtests/i128-bricmp.clif @@ -232,17 +232,3 @@ block2: ; 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(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 -} diff --git a/cranelift/filetests/filetests/runtests/i128-call.clif b/cranelift/filetests/filetests/runtests/i128-call.clif new file mode 100644 index 000000000000..8170b24b3e5e --- /dev/null +++ b/cranelift/filetests/filetests/runtests/i128-call.clif @@ -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 diff --git a/cranelift/filetests/filetests/runtests/simd-bitselect.clif b/cranelift/filetests/filetests/runtests/simd-bitselect.clif index 3ab2187f6053..3febd9868529 100644 --- a/cranelift/filetests/filetests/runtests/simd-bitselect.clif +++ b/cranelift/filetests/filetests/runtests/simd-bitselect.clif @@ -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. ; 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: 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 diff --git a/cranelift/filetests/filetests/runtests/simd-vselect.clif b/cranelift/filetests/filetests/runtests/simd-vselect.clif index b4a1c709136b..1165ae8a0963 100644 --- a/cranelift/filetests/filetests/runtests/simd-vselect.clif +++ b/cranelift/filetests/filetests/runtests/simd-vselect.clif @@ -25,7 +25,7 @@ block0: } ; run: %vselect_i16x8() == [200 101 202 103 204 105 106 107] -function %vselect_i32x4() -> i32x4 { +function %vselect_i32x4_const() -> i32x4 { block0: v1 = vconst.b32x4 [false true false true] v2 = vconst.i32x4 [100 101 102 103] @@ -33,7 +33,15 @@ block0: v4 = vselect v1, v2, v3 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 { block0: @@ -72,15 +80,3 @@ block0(v0: b64x2, v1: i64x2, v2: i64x2): return v3 } ; 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] diff --git a/cranelift/filetests/src/function_runner.rs b/cranelift/filetests/src/function_runner.rs index b4484c72b414..3365f7de4b84 100644 --- a/cranelift/filetests/src/function_runner.rs +++ b/cranelift/filetests/src/function_runner.rs @@ -1,52 +1,106 @@ //! Provides functionality for compiling and running CLIF IR for `run` tests. -use anyhow::Result; +use anyhow::{anyhow, Result}; use core::mem; 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::{ir, settings, CodegenError}; +use cranelift_codegen::{ir, settings, CodegenError, Context}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_jit::{JITBuilder, JITModule}; use cranelift_module::{FuncId, Linkage, Module, ModuleError}; use cranelift_native::builder_with_options; +use cranelift_reader::TestFile; use std::cmp::max; +use std::collections::hash_map::Entry; +use std::collections::HashMap; 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 -/// [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 /// 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-of-function" functionality, see `cranelift_jit::backend::JITBackend`. /// /// ``` -/// use cranelift_filetests::SingleFunctionCompiler; +/// use cranelift_filetests::TestFileCompiler; /// 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 mut compiler = TestFileCompiler::with_default_host_isa().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); /// ``` -pub struct SingleFunctionCompiler { - isa: Box, +pub struct TestFileCompiler { + 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, + + /// 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, } -impl SingleFunctionCompiler { - /// Build a [SingleFunctionCompiler] from a [TargetIsa]. For functions to be runnable on the +impl TestFileCompiler { + /// 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 - /// [SingleFunctionCompiler::with_host_isa]). + /// [TestFileCompiler::with_host_isa]). pub fn new(isa: Box) -> 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 { let builder = builder_with_options(true).expect("Unable to build a TargetIsa for the current host"); @@ -54,144 +108,237 @@ impl SingleFunctionCompiler { 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. pub fn with_default_host_isa() -> Result { let flags = settings::Flags::new(settings::builder()); Self::with_host_isa(flags) } - /// Compile the passed [Function] to a `CompiledFunction`. This function will: - /// - check that the default ISA calling convention is used (to ensure it can be called) - /// - compile the [Function] - /// - compile a `Trampoline` for the [Function]'s signature (or used a cached `Trampoline`; - /// this makes it possible to call functions when the signature is not known until runtime. - pub fn compile(self, function: Function) -> Result { - let signature = function.signature.clone(); - if signature.call_conv != self.isa.default_call_conv() { - return Err(CompilationError::InvalidTargetIsa); + /// Registers all functions in a [TestFile]. Additionally creates a trampoline for each one + /// of them. + pub fn add_testfile(&mut self, testfile: &TestFile) -> Result<()> { + // Declare all functions in the file, so that they may refer to each other. + for (func, _) in &testfile.functions { + self.declare_function(func)?; + } + + // Define all functions and trampolines + for (func, _) in &testfile.functions { + self.define_function(func.clone())?; + self.create_trampoline_for_function(func)?; + } + + Ok(()) + } + + /// Declares a function an registers it as a linkable and callable target internally + 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)?; + + v.insert(DefinedFunction { + new_name: UserExternalName::new(TESTFILE_NAMESPACE, next_id), + signature: func.signature.clone(), + func_id, + }); + } + }; + + Ok(()) + } + + /// Renames the function to its new [UserExternalName], as well as any other function that + /// it may reference. + /// + /// We have to do this since the JIT cannot link Testcase functions. + fn apply_func_rename( + &self, + mut func: Function, + defined_func: &DefinedFunction, + ) -> Result { + // 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); } - let trampoline = make_trampoline(&signature, self.isa.as_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(()) + } - let builder = JITBuilder::with_isa(self.isa, cranelift_module::default_libcall_names()); - let mut module = JITModule::new(builder); - let mut ctx = module.make_context(); + /// 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); + } - let name = function.name.to_string(); - let func_id = module.declare_function(&name, Linkage::Local, &function.signature)?; + // Check if a trampoline for this function signature already exists + if self.trampolines.contains_key(&func.signature) { + return Ok(()); + } - // Build and declare the trampoline in the module - let trampoline_name = trampoline.name.to_string(); - let trampoline_id = - module.declare_function(&trampoline_name, Linkage::Local, &trampoline.signature)?; + // 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()); - // Define both functions - let func_signature = function.signature.clone(); - ctx.func = function; - module.define_function(func_id, &mut ctx)?; - module.clear_context(&mut ctx); + self.declare_function(&trampoline)?; + self.define_function(trampoline)?; - ctx.func = trampoline; - module.define_function(trampoline_id, &mut ctx)?; - module.clear_context(&mut ctx); + self.trampolines.insert(func.signature.clone(), name); + Ok(()) + } + + /// Finalize this TestFile and link all functions. + pub fn compile(mut self) -> Result { // Finalize the functions which we just defined, which resolves any // outstanding relocations (patching in addresses, now that they're // available). - module.finalize_definitions(); + self.module.finalize_definitions(); - Ok(CompiledFunction::new( - module, - func_signature, - func_id, - trampoline_id, - )) + Ok(CompiledTestFile { + module: Some(self.module), + defined_functions: self.defined_functions, + trampolines: self.trampolines, + }) } } -/// Compilation Error when compiling a function. -#[derive(Error, Debug)] -pub enum CompilationError { - /// This Target ISA is invalid for the current host. - #[error("Cross-compilation not currently supported; use the host's default calling convention \ - or remove the specified calling convention in the function signature to use the host's default.")] - InvalidTargetIsa, - /// Cranelift codegen error. - #[error("Cranelift codegen error")] - CodegenError(#[from] CodegenError), - /// Module Error - #[error("Module error")] - ModuleError(#[from] ModuleError), - /// Memory mapping error. - #[error("Memory mapping 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 +/// 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, - signature: Signature, - func_id: FuncId, - trampoline_id: FuncId, + + /// Holds info about the functions that have been registered in `module`. + /// See [TestFileCompiler] for more info. + defined_functions: HashMap, + + /// Trampolines available in this [JITModule]. + /// See [TestFileCompiler] for more info. + trampolines: HashMap, } -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, +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 { + 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, - } + }) } +} + +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() } + } +} - /// Call the [CompiledFunction], passing in [DataValue]s using a compiled trampoline. +/// 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 { - let mut values = UnboxedValues::make_arguments(arguments, &self.signature); + let mut values = UnboxedValues::make_arguments(arguments, &self.func_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 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.signature) + values.collect_returns(&self.func_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() } - } +/// Compilation Error when compiling a function. +#[derive(Error, Debug)] +pub enum CompilationError { + /// This Target ISA is invalid for the current host. + #[error("Cross-compilation not currently supported; use the host's default calling convention \ + or remove the specified calling convention in the function signature to use the host's default.")] + InvalidTargetIsa, + /// Cranelift codegen error. + #[error("Cranelift codegen error")] + CodegenError(#[from] CodegenError), + /// Module Error + #[error("Module error")] + ModuleError(#[from] ModuleError), + /// Memory mapping error. + #[error("Memory mapping error")] + IoError(#[from] std::io::Error), } /// A container for laying out the [ValueData]s in memory in a way that the [Trampoline] can @@ -252,15 +399,15 @@ impl UnboxedValues { /// (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 /// calling convention so we must also check that the [CompiledFunction] has the same calling -/// convention (see [SingleFunctionCompiler::compile]). -fn make_trampoline(signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { +/// convention (see [TestFileCompiler::compile]). +fn make_trampoline(name: UserFuncName, signature: &ir::Signature, isa: &dyn TargetIsa) -> Function { // Create the trampoline signature: (callee_address: pointer, values_vec: pointer) -> () let pointer_type = isa.pointer_type(); 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 `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. let mut builder_context = FunctionBuilderContext::new(); @@ -385,9 +532,13 @@ mod test { let function = test_file.functions[0].0.clone(); // execute function - let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); - let compiled_function = compiler.compile(function).unwrap(); - let returned = compiled_function.call(&[]); + let mut compiler = TestFileCompiler::with_default_host_isa().unwrap(); + compiler.declare_function(&function).unwrap(); + 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)]) } @@ -403,8 +554,12 @@ mod test { }", ); - let compiler = SingleFunctionCompiler::with_default_host_isa().unwrap(); - let trampoline = make_trampoline(&function.signature, compiler.isa.as_ref()); + let compiler = TestFileCompiler::with_default_host_isa().unwrap(); + let trampoline = make_trampoline( + UserFuncName::user(0, 0), + &function.signature, + compiler.module.isa(), + ); assert!(format!("{}", trampoline).ends_with( "sig0 = (f32, i8, i64x2, b1) -> f32x4, b64 fast diff --git a/cranelift/filetests/src/lib.rs b/cranelift/filetests/src/lib.rs index 351d2097e493..9ea743167259 100644 --- a/cranelift/filetests/src/lib.rs +++ b/cranelift/filetests/src/lib.rs @@ -22,7 +22,7 @@ ) )] -pub use crate::function_runner::SingleFunctionCompiler; +pub use crate::function_runner::TestFileCompiler; use crate::runner::TestRunner; use cranelift_codegen::timing; use cranelift_reader::TestCommand; diff --git a/cranelift/filetests/src/runone.rs b/cranelift/filetests/src/runone.rs index fc8df544709d..fe8e04b4eed0 100644 --- a/cranelift/filetests/src/runone.rs +++ b/cranelift/filetests/src/runone.rs @@ -1,17 +1,15 @@ //! Run the tests in a single test file. use crate::new_subtest; -use crate::subtest::{Context, SubTest}; +use crate::subtest::SubTest; use anyhow::{bail, Context as _, Result}; -use cranelift_codegen::ir::Function; use cranelift_codegen::isa::TargetIsa; 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::verify_function; -use cranelift_reader::{parse_test, IsaSpec, Location, ParseOptions}; +use cranelift_reader::{parse_test, IsaSpec, Location, ParseOptions, TestFile}; use log::info; -use std::borrow::Cow; use std::cell::Cell; use std::fs; use std::path::{Path, PathBuf}; @@ -78,39 +76,38 @@ pub fn run( tests.sort_by_key(|st| (st.is_mutating(), st.needs_verifier())); // 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. - // If so, we can completely avoid cloning functions. - let last_tuple = match tuples.pop() { - None => anyhow::bail!("no test commands found"), - Some(t) => t, - }; + // Bail if the test has no runnable commands + if tuples.is_empty() { + anyhow::bail!("no test commands found"); + } let mut file_update = FileUpdate::new(&path); let file_path = path.to_string_lossy(); - for (func, details) in testfile.functions { - let mut context = Context { - preamble_comments: &testfile.preamble_comments, - details, - verified: false, - 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)?; + for (test, flags, isa) in &tuples { + // Should we run the verifier before this test? + if test.needs_verifier() { + let fisa = FlagsOrIsa { flags, isa: *isa }; + verify_testfile(&testfile, fisa)?; } - // Run the last test with an owned function which means it won't need to clone it before - // mutating. - run_one_test(last_tuple, Cow::Owned(func), &mut context)?; + + test.run_target(&testfile, &mut file_update, file_path.as_ref(), flags, *isa)?; } 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. fn test_tuples<'a>( tests: &'a [Box], @@ -141,29 +138,6 @@ fn test_tuples<'a>( Ok(out) } -fn run_one_test<'a>( - tuple: (&'a dyn SubTest, &'a Flags, Option<&'a dyn TargetIsa>), - func: Cow, - 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 /// automatically updated. /// diff --git a/cranelift/filetests/src/subtest.rs b/cranelift/filetests/src/subtest.rs index 3c0f5e41d95b..f5fa4e083782 100644 --- a/cranelift/filetests/src/subtest.rs +++ b/cranelift/filetests/src/subtest.rs @@ -5,8 +5,9 @@ use anyhow::Context as _; use cranelift_codegen::ir::Function; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{Flags, FlagsOrIsa}; -use cranelift_reader::{Comment, Details}; +use cranelift_reader::{Comment, Details, TestFile}; use filecheck::{Checker, CheckerBuilder, NO_VARIABLES}; +use log::info; use std::borrow::Cow; /// Context for running a test on a single function. @@ -15,10 +16,7 @@ pub struct Context<'a> { pub preamble_comments: &'a [Comment<'a>], /// Additional details about the function from the parser. - pub details: Details<'a>, - - /// Was the function verified before running this test? - pub verified: bool, + pub details: &'a Details<'a>, /// ISA-independent flags for this test. pub flags: &'a Flags, @@ -69,6 +67,40 @@ pub trait SubTest { 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`. fn run(&self, func: Cow, context: &Context) -> anyhow::Result<()>; } diff --git a/cranelift/filetests/src/test_run.rs b/cranelift/filetests/src/test_run.rs index a817f142336e..f41f64ef9d80 100644 --- a/cranelift/filetests/src/test_run.rs +++ b/cranelift/filetests/src/test_run.rs @@ -2,17 +2,19 @@ //! //! 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::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; +use cranelift_codegen::settings::{Configurable, Flags}; use cranelift_codegen::{ir, settings}; -use cranelift_reader::parse_run_command; use cranelift_reader::TestCommand; -use log::trace; +use cranelift_reader::{parse_run_command, TestFile}; +use log::{info, trace}; use std::borrow::Cow; 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. fn is_isa_compatible( - context: &Context, + file_path: &str, host: &dyn TargetIsa, requested: &dyn TargetIsa, ) -> Result<(), String> { @@ -71,7 +73,7 @@ fn is_isa_compatible( if host_arch != requested_arch { return Err(format!( "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 { return Err(format!( "skipped {}: host does not support ISA flag {}", - context.file_path, req_value.name + file_path, req_value.name )); } } else { @@ -101,6 +103,54 @@ fn is_isa_compatible( Ok(()) } +fn compile_testfile( + testfile: &TestFile, + flags: &Flags, + isa: &dyn TargetIsa, +) -> anyhow::Result { + // 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" @@ -114,10 +164,19 @@ impl SubTest for TestRun { true } - fn run(&self, func: Cow, 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. // 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!([ "Cannot run runtests with pinned_reg enabled.", "See https://github.com/bytecodealliance/wasmtime/issues/4376 for more info" @@ -125,46 +184,41 @@ impl SubTest for TestRun { .join("\n"))); } - let host_isa = build_host_isa(true, context.flags.clone(), vec![]); - let requested_isa = context.isa.unwrap(); - if let Err(e) = is_isa_compatible(context, host_isa.as_ref(), requested_isa) { + // 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 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); - - // 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, context.flags.clone(), requested_isa.isa_flags()); - - let compiled_fn = - 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))?; - } + 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, _context: &Context) -> anyhow::Result<()> { + unreachable!() + } } /// Build a VMContext struct with the layout described in docs/testing.md. diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs index c2a513423ea7..bd10e1762a79 100644 --- a/cranelift/src/run.rs +++ b/cranelift/src/run.rs @@ -4,7 +4,7 @@ use crate::utils::{iterate_files, read_to_string}; use anyhow::Result; use clap::Parser; use cranelift_codegen::isa::{CallConv, TargetIsa}; -use cranelift_filetests::SingleFunctionCompiler; +use cranelift_filetests::TestFileCompiler; use cranelift_native::builder as host_isa_builder; use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions}; use std::path::{Path, PathBuf}; @@ -85,13 +85,18 @@ fn run_file_contents(file_contents: String) -> Result<()> { ..ParseOptions::default() }; 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 comment in comments { if let Some(command) = parse_run_command(comment.text, &func.signature)? { - let isa = create_target_isa(&test_file.isa_spec)?; - let compiled_fn = SingleFunctionCompiler::new(isa).compile(func.clone())?; + let trampoline = compiled.get_trampoline(&func).unwrap(); + command - .run(|_, args| Ok(compiled_fn.call(args))) + .run(|_, args| Ok(trampoline.call(args))) .map_err(|s| anyhow::anyhow!("{}", s))?; } } diff --git a/fuzz/fuzz_targets/cranelift-fuzzgen.rs b/fuzz/fuzz_targets/cranelift-fuzzgen.rs index f4edd7442100..4df01044291b 100644 --- a/fuzz/fuzz_targets/cranelift-fuzzgen.rs +++ b/fuzz/fuzz_targets/cranelift-fuzzgen.rs @@ -5,7 +5,7 @@ use libfuzzer_sys::fuzz_target; use cranelift_codegen::data_value::DataValue; use cranelift_codegen::settings; use cranelift_codegen::settings::Configurable; -use cranelift_filetests::function_runner::{CompiledFunction, SingleFunctionCompiler}; +use cranelift_filetests::function_runner::{TestFileCompiler, Trampoline}; use cranelift_fuzzgen::*; use cranelift_interpreter::environment::FuncIndex; 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 { - let res = compiled_fn.call(args); +fn run_in_host(trampoline: &Trampoline, args: &[DataValue]) -> RunResult { + let res = trampoline.call(args); RunResult::Success(res) } @@ -68,8 +68,14 @@ fuzz_target!(|testcase: TestCase| { builder.set("enable_llvm_abi_extensions", "true").unwrap(); settings::Flags::new(builder) }; - let host_compiler = SingleFunctionCompiler::with_host_isa(flags).unwrap(); - let compiled_fn = host_compiler.compile(testcase.func.clone()).unwrap(); + let mut compiler = TestFileCompiler::with_host_isa(flags).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 { // 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), } - let host_res = run_in_host(&compiled_fn, args); + let host_res = run_in_host(&trampoline, args); match host_res { RunResult::Success(_) => {} _ => panic!("host failed: {:?}", host_res),