// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "src/assembler-inl.h" #include "src/objects-inl.h" #include "src/wasm/wasm-objects.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/value-helper.h" #include "test/cctest/wasm/wasm-run-utils.h" #include "test/common/wasm/wasm-macro-gen.h" namespace v8 { namespace internal { namespace wasm { /** * We test the interface from C to compiled wasm code by generating a wasm * function, creating a corresponding signature, compiling the c wasm entry for * that signature, and then calling that entry using different test values. * The result is compared against the expected result, computed from a lambda * passed to the CWasmEntryArgTester. */ namespace { template class CWasmEntryArgTester { public: CWasmEntryArgTester(std::initializer_list wasm_function_bytes, std::function expected_fn) : runner_(kExecuteTurbofan), isolate_(runner_.main_isolate()), expected_fn_(expected_fn), sig_(runner_.template CreateSig()) { std::vector code{wasm_function_bytes}; runner_.Build(code.data(), code.data() + code.size()); wasm_code_ = runner_.builder().GetFunctionCode(0); Handle instance(runner_.builder().instance_object()); Handle debug_info = WasmInstanceObject::GetOrCreateDebugInfo(instance); c_wasm_entry_fn_ = WasmDebugInfo::GetCWasmEntry(debug_info, sig_); } template void WriteToBuffer(uint8_t* buf, Rest... rest) { static_assert(sizeof...(rest) == 0, "this is the base case"); } template void WriteToBuffer(uint8_t* buf, First first, Rest... rest) { WriteUnalignedValue(buf, first); WriteToBuffer(buf + sizeof(first), rest...); } void CheckCall(Args... args) { std::vector arg_buffer(sizeof...(args) * 8); WriteToBuffer(arg_buffer.data(), args...); Handle receiver = isolate_->factory()->undefined_value(); Handle buffer_obj(reinterpret_cast(arg_buffer.data()), isolate_); CHECK(!buffer_obj->IsHeapObject()); Handle call_args[]{ (FLAG_wasm_jit_to_native ? Handle::cast(isolate_->factory()->NewForeign( wasm_code_.GetWasmCode()->instructions().start(), TENURED)) : Handle::cast(wasm_code_.GetCode())), buffer_obj}; static_assert( arraysize(call_args) == compiler::CWasmEntryParameters::kNumParameters, "adapt this test"); if (FLAG_wasm_jit_to_native) { wasm_code_.GetWasmCode()->owner()->SetExecutable(true); } MaybeHandle return_obj = Execution::Call( isolate_, c_wasm_entry_fn_, receiver, arraysize(call_args), call_args); CHECK(!return_obj.is_null()); CHECK(return_obj.ToHandleChecked()->IsSmi()); CHECK_EQ(0, Smi::ToInt(*return_obj.ToHandleChecked())); // Check the result. ReturnType result = ReadUnalignedValue(arg_buffer.data()); ReturnType expected = expected_fn_(args...); if (std::is_floating_point::value) { CHECK_DOUBLE_EQ(expected, result); } else { CHECK_EQ(expected, result); } } private: WasmRunner runner_; Isolate* isolate_; std::function expected_fn_; FunctionSig* sig_; Handle c_wasm_entry_fn_; WasmCodeWrapper wasm_code_; }; } // namespace // Pass int32_t, return int32_t. TEST(TestCWasmEntryArgPassing_int32) { CWasmEntryArgTester tester( {// Return 2*<0> + 1. WASM_I32_ADD(WASM_I32_MUL(WASM_I32V_1(2), WASM_GET_LOCAL(0)), WASM_ONE)}, [](int32_t a) { return 2 * a + 1; }); FOR_INT32_INPUTS(v) { tester.CheckCall(*v); } } // Pass int64_t, return double. TEST(TestCWasmEntryArgPassing_double_int64) { CWasmEntryArgTester tester( {// Return (double)<0>. WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(0))}, [](int64_t a) { return static_cast(a); }); FOR_INT64_INPUTS(v) { tester.CheckCall(*v); } } // Pass double, return int64_t. TEST(TestCWasmEntryArgPassing_int64_double) { CWasmEntryArgTester tester( {// Return (int64_t)<0>. WASM_I64_SCONVERT_F64(WASM_GET_LOCAL(0))}, [](double d) { return static_cast(d); }); FOR_INT64_INPUTS(i) { tester.CheckCall(*i); } } // Pass float, return double. TEST(TestCWasmEntryArgPassing_float_double) { CWasmEntryArgTester tester( {// Return 2*(double)<0> + 1. WASM_F64_ADD( WASM_F64_MUL(WASM_F64(2), WASM_F64_CONVERT_F32(WASM_GET_LOCAL(0))), WASM_F64(1))}, [](float f) { return 2. * static_cast(f) + 1.; }); FOR_FLOAT32_INPUTS(f) { tester.CheckCall(*f); } } // Pass two doubles, return double. TEST(TestCWasmEntryArgPassing_double_double) { CWasmEntryArgTester tester( {// Return <0> + <1>. WASM_F64_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))}, [](double a, double b) { return a + b; }); FOR_FLOAT64_INPUTS(d1) { FOR_FLOAT64_INPUTS(d2) { tester.CheckCall(*d1, *d2); } } } // Pass int32_t, int64_t, float and double, return double. TEST(TestCWasmEntryArgPassing_AllTypes) { CWasmEntryArgTester tester( { // Convert all arguments to double, add them and return the sum. WASM_F64_ADD( // <0+1+2> + <3> WASM_F64_ADD( // <0+1> + <2> WASM_F64_ADD( // <0> + <1> WASM_F64_SCONVERT_I32( WASM_GET_LOCAL(0)), // <0> to double WASM_F64_SCONVERT_I64( WASM_GET_LOCAL(1))), // <1> to double WASM_F64_CONVERT_F32(WASM_GET_LOCAL(2))), // <2> to double WASM_GET_LOCAL(3)) // <3> }, [](int32_t a, int64_t b, float c, double d) { return 0. + a + b + c + d; }); Vector test_values_i32 = compiler::ValueHelper::int32_vector(); Vector test_values_i64 = compiler::ValueHelper::int64_vector(); Vector test_values_f32 = compiler::ValueHelper::float32_vector(); Vector test_values_f64 = compiler::ValueHelper::float64_vector(); size_t max_len = std::max(std::max(test_values_i32.size(), test_values_i64.size()), std::max(test_values_f32.size(), test_values_f64.size())); for (size_t i = 0; i < max_len; ++i) { int32_t i32 = test_values_i32[i % test_values_i32.size()]; int64_t i64 = test_values_i64[i % test_values_i64.size()]; float f32 = test_values_f32[i % test_values_f32.size()]; double f64 = test_values_f64[i % test_values_f64.size()]; tester.CheckCall(i32, i64, f32, f64); } } } // namespace wasm } // namespace internal } // namespace v8