From cbc7dc84544034f708af095913edfabac161583d Mon Sep 17 00:00:00 2001 From: Nick Lewycky Date: Wed, 3 Jun 2020 17:07:20 -0700 Subject: [PATCH] Add multi-value-import tests. Also fix implementation of trampolines in LLVM. --- lib/compiler-llvm/src/trampoline/wasm.rs | 102 ++++++++---- tests/compilers/main.rs | 1 + tests/compilers/multi_value_imports.rs | 204 +++++++++++++++++++++++ 3 files changed, 275 insertions(+), 32 deletions(-) create mode 100644 tests/compilers/multi_value_imports.rs diff --git a/lib/compiler-llvm/src/trampoline/wasm.rs b/lib/compiler-llvm/src/trampoline/wasm.rs index cb5671ef8..3a578013b 100644 --- a/lib/compiler-llvm/src/trampoline/wasm.rs +++ b/lib/compiler-llvm/src/trampoline/wasm.rs @@ -1,6 +1,9 @@ use crate::config::{CompiledFunctionKind, LLVMConfig}; use crate::object_file::load_object_file; -use crate::translator::abi::{func_type_to_llvm, is_sret, rets_from_call}; +use crate::translator::abi::{ + func_type_to_llvm, get_vmctx_ptr_param, is_sret, pack_values_for_register_return, + rets_from_call, +}; use crate::translator::intrinsics::{type_to_llvm, type_to_llvm_ptr, Intrinsics}; use inkwell::{ attributes::{Attribute, AttributeLoc}, @@ -14,7 +17,6 @@ use inkwell::{ }; use std::cmp; use std::convert::TryInto; -use std::iter; use wasm_common::{FunctionType, Type}; use wasmer_compiler::{CompileError, FunctionBody}; @@ -134,16 +136,11 @@ impl FuncTrampoline { module.set_data_layout(&target_machine.get_target_data().get_data_layout()); let intrinsics = Intrinsics::declare(&module, &self.ctx); - let params = iter::once(Ok(intrinsics.ctx_ptr_ty.as_basic_type_enum())) - .chain( - ty.params() - .iter() - .map(|param_ty| type_to_llvm(&intrinsics, *param_ty)), - ) - .collect::, _>>()?; - let trampoline_ty = intrinsics.void_ty.fn_type(params.as_slice(), false); - + let (trampoline_ty, trampoline_attrs) = func_type_to_llvm(&self.ctx, &intrinsics, ty)?; let trampoline_func = module.add_function("", trampoline_ty, Some(Linkage::External)); + for (attr, attr_loc) in trampoline_attrs { + trampoline_func.add_attribute(attr_loc, attr); + } trampoline_func .as_global_value() .set_section(FUNCTION_SECTION); @@ -343,7 +340,12 @@ fn generate_dynamic_trampoline<'ctx>( let ptr = builder .build_bitcast(ptr, type_to_llvm_ptr(intrinsics, func_sig.params()[i])?, "") .into_pointer_value(); - builder.build_store(ptr, trampoline_func.get_nth_param(i as u32 + 1).unwrap()); + builder.build_store( + ptr, + trampoline_func + .get_nth_param(i as u32 + if is_sret(func_sig)? { 2 } else { 1 }) + .unwrap(), + ); } let callee_ty = intrinsics @@ -358,7 +360,7 @@ fn generate_dynamic_trampoline<'ctx>( .ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic); - let vmctx = trampoline_func.get_nth_param(0).unwrap(); + let vmctx = get_vmctx_ptr_param(&trampoline_func); let callee = builder .build_load( builder @@ -368,32 +370,68 @@ fn generate_dynamic_trampoline<'ctx>( ) .into_pointer_value(); + let values_ptr = builder.build_pointer_cast(values, intrinsics.i128_ptr_ty, ""); builder.build_call( callee, - &[vmctx.as_basic_value_enum(), values.as_basic_value_enum()], + &[ + vmctx.as_basic_value_enum(), + values_ptr.as_basic_value_enum(), + ], "", ); - match func_sig.results() { - [] => { - builder.build_return(None); - } - [ty] => { - let ptr = unsafe { - builder.build_in_bounds_gep( - values, - &[intrinsics.i32_zero, intrinsics.i32_ty.const_int(0, false)], - "", - ) - }; - let ptr = builder - .build_bitcast(ptr, type_to_llvm_ptr(intrinsics, *ty)?, "") + if func_sig.results().is_empty() { + builder.build_return(None); + } else { + let results = func_sig + .results() + .iter() + .enumerate() + .map(|(idx, ty)| { + let ptr = unsafe { + builder.build_gep( + values, + &[intrinsics.i32_ty.const_int(idx.try_into().unwrap(), false)], + "", + ) + }; + let ptr = builder.build_pointer_cast(ptr, type_to_llvm_ptr(intrinsics, *ty)?, ""); + Ok(builder.build_load(ptr, "")) + }) + .collect::, CompileError>>()?; + + if is_sret(func_sig)? { + let sret = trampoline_func + .get_first_param() + .unwrap() .into_pointer_value(); - let ret = builder.build_load(ptr, ""); - builder.build_return(Some(&ret)); + let mut struct_value = sret + .get_type() + .get_element_type() + .into_struct_type() + .get_undef(); + for (idx, value) in results.iter().enumerate() { + let value = builder.build_bitcast( + *value, + type_to_llvm(&intrinsics, func_sig.results()[idx])?, + "", + ); + struct_value = builder + .build_insert_value(struct_value, value, idx as u32, "") + .unwrap() + .into_struct_value(); + } + builder.build_store(sret, struct_value); + builder.build_return(None); + } else { + builder.build_return(Some(&pack_values_for_register_return( + &intrinsics, + &builder, + &results.as_slice(), + &trampoline_func.get_type(), + )?)); } - _ => unimplemented!("multi-value return is not yet implemented"), - }; + } Ok(()) } diff --git a/tests/compilers/main.rs b/tests/compilers/main.rs index a02d5995a..cd6413307 100644 --- a/tests/compilers/main.rs +++ b/tests/compilers/main.rs @@ -5,4 +5,5 @@ #[macro_use] mod macros; mod imports; +mod multi_value_imports; mod wast; diff --git a/tests/compilers/multi_value_imports.rs b/tests/compilers/multi_value_imports.rs new file mode 100644 index 000000000..7e4df360b --- /dev/null +++ b/tests/compilers/multi_value_imports.rs @@ -0,0 +1,204 @@ +//! Testing the imports with different provided functions. +//! This tests checks that the provided functions (both native and +//! dynamic ones) work properly. + +macro_rules! mvr_test { + ($test_name:ident, $( $result_type:ty ),* ) => { + mod $test_name { + use super::*; + + fn get_module(store: &Store) -> anyhow::Result { + let wat: String = r#" + (type $type (func (param i32) (result +"#.to_string() + + &stringify!( $( $result_type ),* ).replace(",", "").replace("(", "").replace(")", "") + &r#"))) + (import "host" "callback_fn" (func $callback_fn (type $type))) + (func (export "test_call") (type $type) + get_local 0 + call $callback_fn) + (func (export "test_call_indirect") (type $type) + (i32.const 1) + (call_indirect (type $type) (i32.const 0)) + ) + (table funcref + (elem + $callback_fn + ) + ) +"#.to_string(); + Ok(wasmer::Module::new(&store, &wat)?) + } + + fn callback_fn(n: i32) -> ( $( $result_type ),* ) { + ( $( <$result_type>::expected_value(n) ),* ) + } + + #[test] + fn native() -> anyhow::Result<()> { + let store = get_store(); + let module = get_module(&store)?; + let instance = wasmer::Instance::new( + &module, + &wasmer::imports! { + "host" => { + "callback_fn" => wasmer::Function::new(&store, callback_fn) + } + } + )?; + let expected_value = vec![ $( <$result_type>::expected_val(1) ),* ].into_boxed_slice(); + assert_eq!(instance.exports.get_function("test_call")?.call(&[wasmer::Val::I32(1)])?, + expected_value); + assert_eq!(instance.exports.get_function("test_call_indirect")?.call(&[wasmer::Val::I32(1)])?, + expected_value); + Ok(()) + } + + fn dynamic_callback_fn(values: &[wasmer::Value]) -> Result, wasmer::RuntimeError> { + assert_eq!(values[0], wasmer::Value::I32(1)); + Ok(vec![ $( <$result_type>::expected_val(1) ),* ]) + } + + #[test] + fn dynamic() -> anyhow::Result<()> { + let store = get_store(); + let module = get_module(&store)?; + let instance = wasmer::Instance::new( + &module, + &wasmer::imports! { + "host" => { + "callback_fn" => wasmer::Function::new_dynamic(&store, &wasmer::FunctionType::new(vec![wasmer::ValType::I32], vec![ $( <$result_type>::expected_valtype() ),* ]), dynamic_callback_fn) + } + } + )?; + let expected_value = vec![ $( <$result_type>::expected_val(1) ),* ].into_boxed_slice(); + assert_eq!(instance.exports.get_function("test_call")?.call(&[wasmer::Val::I32(1)])?, + expected_value); + assert_eq!(instance.exports.get_function("test_call_indirect")?.call(&[wasmer::Val::I32(1)])?, + expected_value); + Ok(()) + } + } + } +} + +wasmer_compilers! { +trait ExpectedExpr { + fn expected_value(n: i32) -> Self; + fn expected_val(n: i32) -> wasmer::Val; + fn expected_valtype() -> wasmer::ValType; +} +impl ExpectedExpr for i32 { + fn expected_value(n: i32) -> i32 { + n + 1 + } + fn expected_val(n: i32) -> wasmer::Val { + wasmer::Val::I32(Self::expected_value(n)) + } + fn expected_valtype() -> wasmer::ValType { + wasmer::ValType::I32 + } +} +impl ExpectedExpr for i64 { + fn expected_value(n: i32) -> i64 { + n as i64 + 2i64 + } + fn expected_val(n: i32) -> wasmer::Val { + wasmer::Val::I64(Self::expected_value(n)) + } + fn expected_valtype() -> wasmer::ValType { + wasmer::ValType::I64 + } +} +impl ExpectedExpr for f32 { + fn expected_value(n: i32) -> f32 { + n as f32 * 0.1 + } + fn expected_val(n: i32) -> wasmer::Val { + wasmer::Val::F32(Self::expected_value(n)) + } + fn expected_valtype() -> wasmer::ValType { + wasmer::ValType::F32 + } +} +impl ExpectedExpr for f64 { + fn expected_value(n: i32) -> f64 { + n as f64 * 0.12 + } + fn expected_val(n: i32) -> wasmer::Val { + wasmer::Val::F64(Self::expected_value(n)) + } + fn expected_valtype() -> wasmer::ValType { + wasmer::ValType::F64 + } +} + + mvr_test!(test_mvr_i32_i32, i32, i32); +mvr_test!(test_mvr_i32_f32, i32, f32); +mvr_test!(test_mvr_f32_i32, f32, i32); +mvr_test!(test_mvr_f32_f32, f32, f32); + +mvr_test!(test_mvr_i64_i32, i64, i32); +mvr_test!(test_mvr_i64_f32, i64, f32); +mvr_test!(test_mvr_f64_i32, f64, i32); +mvr_test!(test_mvr_f64_f32, f64, f32); + +mvr_test!(test_mvr_i32_i64, i32, i64); +mvr_test!(test_mvr_f32_i64, f32, i64); +mvr_test!(test_mvr_i32_f64, i32, f64); +mvr_test!(test_mvr_f32_f64, f32, f64); + +mvr_test!(test_mvr_i32_i32_i32, i32, i32, i32); +mvr_test!(test_mvr_i32_i32_f32, i32, i32, f32); +mvr_test!(test_mvr_i32_f32_i32, i32, f32, i32); +mvr_test!(test_mvr_i32_f32_f32, i32, f32, f32); +mvr_test!(test_mvr_f32_i32_i32, f32, i32, i32); +mvr_test!(test_mvr_f32_i32_f32, f32, i32, f32); +mvr_test!(test_mvr_f32_f32_i32, f32, f32, i32); +mvr_test!(test_mvr_f32_f32_f32, f32, f32, f32); + +mvr_test!(test_mvr_i32_i32_i64, i32, i32, i64); +mvr_test!(test_mvr_i32_f32_i64, i32, f32, i64); +mvr_test!(test_mvr_f32_i32_i64, f32, i32, i64); +mvr_test!(test_mvr_f32_f32_i64, f32, f32, i64); +mvr_test!(test_mvr_i32_i32_f64, i32, i32, f64); +mvr_test!(test_mvr_i32_f32_f64, i32, f32, f64); +mvr_test!(test_mvr_f32_i32_f64, f32, i32, f64); +mvr_test!(test_mvr_f32_f32_f64, f32, f32, f64); + +mvr_test!(test_mvr_i32_i64_i32, i32, i64, i32); +mvr_test!(test_mvr_i32_i64_f32, i32, i64, f32); +mvr_test!(test_mvr_f32_i64_i32, f32, i64, i32); +mvr_test!(test_mvr_f32_i64_f32, f32, i64, f32); +mvr_test!(test_mvr_i32_f64_i32, i32, f64, i32); +mvr_test!(test_mvr_i32_f64_f32, i32, f64, f32); +mvr_test!(test_mvr_f32_f64_i32, f32, f64, i32); +mvr_test!(test_mvr_f32_f64_f32, f32, f64, f32); + +mvr_test!(test_mvr_i64_i32_i32, i64, i32, i32); +mvr_test!(test_mvr_i64_i32_f32, i64, i32, f32); +mvr_test!(test_mvr_i64_f32_i32, i64, f32, i32); +mvr_test!(test_mvr_i64_f32_f32, i64, f32, f32); +mvr_test!(test_mvr_f64_i32_i32, f64, i32, i32); +mvr_test!(test_mvr_f64_i32_f32, f64, i32, f32); +mvr_test!(test_mvr_f64_f32_i32, f64, f32, i32); +mvr_test!(test_mvr_f64_f32_f32, f64, f32, f32); + +mvr_test!(test_mvr_i32_i32_i32_i32, i32, i32, i32, i32); +mvr_test!(test_mvr_i32_i32_i32_f32, i32, i32, i32, f32); +mvr_test!(test_mvr_i32_i32_f32_i32, i32, i32, f32, i32); +mvr_test!(test_mvr_i32_i32_f32_f32, i32, i32, f32, f32); +mvr_test!(test_mvr_i32_f32_i32_i32, i32, f32, i32, i32); +mvr_test!(test_mvr_i32_f32_i32_f32, i32, f32, i32, f32); +mvr_test!(test_mvr_i32_f32_f32_i32, i32, f32, f32, i32); +mvr_test!(test_mvr_i32_f32_f32_f32, i32, f32, f32, f32); +mvr_test!(test_mvr_f32_i32_i32_i32, f32, i32, i32, i32); +mvr_test!(test_mvr_f32_i32_i32_f32, f32, i32, i32, f32); +mvr_test!(test_mvr_f32_i32_f32_i32, f32, i32, f32, i32); +mvr_test!(test_mvr_f32_i32_f32_f32, f32, i32, f32, f32); +mvr_test!(test_mvr_f32_f32_i32_i32, f32, f32, i32, i32); +mvr_test!(test_mvr_f32_f32_i32_f32, f32, f32, i32, f32); +mvr_test!(test_mvr_f32_f32_f32_i32, f32, f32, f32, i32); +mvr_test!(test_mvr_f32_f32_f32_f32, f32, f32, f32, f32); + +mvr_test!(test_mvr_i32_i32_i32_i32_i32, i32, i32, i32, i32, i32); +}