test-c-wasm-entry.cc 7.11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// 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 <cstdint>

#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 <typename ReturnType, typename... Args>
class CWasmEntryArgTester {
 public:
  CWasmEntryArgTester(std::initializer_list<uint8_t> wasm_function_bytes,
                      std::function<ReturnType(Args...)> expected_fn)
33
      : runner_(ExecutionTier::kOptimized),
34 35 36 37 38
        isolate_(runner_.main_isolate()),
        expected_fn_(expected_fn),
        sig_(runner_.template CreateSig<ReturnType, Args...>()) {
    std::vector<uint8_t> code{wasm_function_bytes};
    runner_.Build(code.data(), code.data() + code.size());
39 40
    wasm_code_ = runner_.builder().GetFunctionCode(0);
    Handle<WasmInstanceObject> instance(runner_.builder().instance_object());
41 42 43 44 45 46
    Handle<WasmDebugInfo> debug_info =
        WasmInstanceObject::GetOrCreateDebugInfo(instance);
    c_wasm_entry_fn_ = WasmDebugInfo::GetCWasmEntry(debug_info, sig_);
  }

  template <typename... Rest>
47
  void WriteToBuffer(Address buf, Rest... rest) {
48 49 50 51
    static_assert(sizeof...(rest) == 0, "this is the base case");
  }

  template <typename First, typename... Rest>
52
  void WriteToBuffer(Address buf, First first, Rest... rest) {
53 54 55 56 57 58
    WriteUnalignedValue(buf, first);
    WriteToBuffer(buf + sizeof(first), rest...);
  }

  void CheckCall(Args... args) {
    std::vector<uint8_t> arg_buffer(sizeof...(args) * 8);
59
    WriteToBuffer(reinterpret_cast<Address>(arg_buffer.data()), args...);
60 61 62 63 64

    Handle<Object> receiver = isolate_->factory()->undefined_value();
    Handle<Object> buffer_obj(reinterpret_cast<Object*>(arg_buffer.data()),
                              isolate_);
    CHECK(!buffer_obj->IsHeapObject());
65 66 67 68 69
    Handle<Object> code_entry_obj(
        reinterpret_cast<Object*>(wasm_code_->instruction_start()), isolate_);
    CHECK(!code_entry_obj->IsHeapObject());
    Handle<Object> call_args[]{code_entry_obj,
                               runner_.builder().instance_object(), buffer_obj};
70 71 72
    static_assert(
        arraysize(call_args) == compiler::CWasmEntryParameters::kNumParameters,
        "adapt this test");
73
    wasm_code_->native_module()->SetExecutable(true);
74 75 76 77 78 79 80
    MaybeHandle<Object> 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.
81 82
    ReturnType result = ReadUnalignedValue<ReturnType>(
        reinterpret_cast<Address>(arg_buffer.data()));
83 84 85 86 87 88 89 90 91 92 93 94 95 96
    ReturnType expected = expected_fn_(args...);
    if (std::is_floating_point<ReturnType>::value) {
      CHECK_DOUBLE_EQ(expected, result);
    } else {
      CHECK_EQ(expected, result);
    }
  }

 private:
  WasmRunner<ReturnType, Args...> runner_;
  Isolate* isolate_;
  std::function<ReturnType(Args...)> expected_fn_;
  FunctionSig* sig_;
  Handle<JSFunction> c_wasm_entry_fn_;
97
  WasmCode* wasm_code_;
98 99 100 101 102 103 104 105 106 107 108
};

}  // namespace

// Pass int32_t, return int32_t.
TEST(TestCWasmEntryArgPassing_int32) {
  CWasmEntryArgTester<int32_t, int32_t> 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; });

109
  FOR_INT32_INPUTS(v) { tester.CheckCall(*v); }
110 111 112 113 114 115 116 117 118
}

// Pass int64_t, return double.
TEST(TestCWasmEntryArgPassing_double_int64) {
  CWasmEntryArgTester<double, int64_t> tester(
      {// Return (double)<0>.
       WASM_F64_SCONVERT_I64(WASM_GET_LOCAL(0))},
      [](int64_t a) { return static_cast<double>(a); });

119
  FOR_INT64_INPUTS(v) { tester.CheckCall(*v); }
120 121 122 123 124 125 126 127 128
}

// Pass double, return int64_t.
TEST(TestCWasmEntryArgPassing_int64_double) {
  CWasmEntryArgTester<int64_t, double> tester(
      {// Return (int64_t)<0>.
       WASM_I64_SCONVERT_F64(WASM_GET_LOCAL(0))},
      [](double d) { return static_cast<int64_t>(d); });

129
  FOR_INT64_INPUTS(i) { tester.CheckCall(*i); }
130 131 132 133 134 135 136 137 138 139 140
}

// Pass float, return double.
TEST(TestCWasmEntryArgPassing_float_double) {
  CWasmEntryArgTester<double, float> 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<double>(f) + 1.; });

141
  FOR_FLOAT32_INPUTS(f) { tester.CheckCall(*f); }
142 143 144 145 146 147 148 149 150
}

// Pass two doubles, return double.
TEST(TestCWasmEntryArgPassing_double_double) {
  CWasmEntryArgTester<double, double, double> tester(
      {// Return <0> + <1>.
       WASM_F64_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1))},
      [](double a, double b) { return a + b; });

151 152
  FOR_FLOAT64_INPUTS(d1) {
    FOR_FLOAT64_INPUTS(d2) { tester.CheckCall(*d1, *d2); }
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
  }
}

// Pass int32_t, int64_t, float and double, return double.
TEST(TestCWasmEntryArgPassing_AllTypes) {
  CWasmEntryArgTester<double, int32_t, int64_t, float, double> 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;
      });

175 176 177 178 179
  Vector<const int32_t> test_values_i32 = compiler::ValueHelper::int32_vector();
  Vector<const int64_t> test_values_i64 = compiler::ValueHelper::int64_vector();
  Vector<const float> test_values_f32 = compiler::ValueHelper::float32_vector();
  Vector<const double> test_values_f64 =
      compiler::ValueHelper::float64_vector();
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
  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