C++程序  |  764行  |  26.58 KB

// Copyright 2016 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.

#ifndef WASM_RUN_UTILS_H
#define WASM_RUN_UTILS_H

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "src/base/accounting-allocator.h"
#include "src/base/utils/random-number-generator.h"

#include "src/compiler/graph-visualizer.h"
#include "src/compiler/int64-lowering.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/wasm-compiler.h"
#include "src/compiler/zone-pool.h"

#include "src/wasm/ast-decoder.h"
#include "src/wasm/wasm-interpreter.h"
#include "src/wasm/wasm-js.h"
#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"

#include "src/zone.h"

#include "test/cctest/cctest.h"
#include "test/cctest/compiler/call-tester.h"
#include "test/cctest/compiler/graph-builder-tester.h"

static const uint32_t kMaxFunctions = 10;

enum WasmExecutionMode { kExecuteInterpreted, kExecuteCompiled };

// TODO(titzer): check traps more robustly in tests.
// Currently, in tests, we just return 0xdeadbeef from the function in which
// the trap occurs if the runtime context is not available to throw a JavaScript
// exception.
#define CHECK_TRAP32(x) \
  CHECK_EQ(0xdeadbeef, (bit_cast<uint32_t>(x)) & 0xFFFFFFFF)
#define CHECK_TRAP64(x) \
  CHECK_EQ(0xdeadbeefdeadbeef, (bit_cast<uint64_t>(x)) & 0xFFFFFFFFFFFFFFFF)
#define CHECK_TRAP(x) CHECK_TRAP32(x)

#define WASM_RUNNER_MAX_NUM_PARAMETERS 4
#define WASM_WRAPPER_RETURN_VALUE 8754

#define BUILD(r, ...)                      \
  do {                                     \
    byte code[] = {__VA_ARGS__};           \
    r.Build(code, code + arraysize(code)); \
  } while (false)

namespace {
using namespace v8::base;
using namespace v8::internal;
using namespace v8::internal::compiler;
using namespace v8::internal::wasm;

const uint32_t kMaxGlobalsSize = 128;

// A helper for module environments that adds the ability to allocate memory
// and global variables. Contains a built-in {WasmModule} and
// {WasmModuleInstance}.
class TestingModule : public ModuleEnv {
 public:
  explicit TestingModule(WasmExecutionMode mode = kExecuteCompiled)
      : execution_mode_(mode),
        instance_(&module_),
        isolate_(CcTest::InitIsolateOnce()),
        global_offset(0),
        interpreter_(mode == kExecuteInterpreted
                         ? new WasmInterpreter(&instance_, &allocator_)
                         : nullptr) {
    module = &module_;
    instance = &instance_;
    instance->module = &module_;
    instance->globals_start = global_data;
    module_.globals_size = kMaxGlobalsSize;
    instance->mem_start = nullptr;
    instance->mem_size = 0;
    origin = kWasmOrigin;
    memset(global_data, 0, sizeof(global_data));
  }

  ~TestingModule() {
    if (instance->mem_start) {
      free(instance->mem_start);
    }
    if (interpreter_) delete interpreter_;
  }

  byte* AddMemory(uint32_t size) {
    CHECK_NULL(instance->mem_start);
    CHECK_EQ(0, instance->mem_size);
    instance->mem_start = reinterpret_cast<byte*>(malloc(size));
    CHECK(instance->mem_start);
    memset(instance->mem_start, 0, size);
    instance->mem_size = size;
    return raw_mem_start<byte>();
  }

  template <typename T>
  T* AddMemoryElems(uint32_t count) {
    AddMemory(count * sizeof(T));
    return raw_mem_start<T>();
  }

  template <typename T>
  T* AddGlobal(MachineType mem_type) {
    const WasmGlobal* global = AddGlobal(mem_type);
    return reinterpret_cast<T*>(instance->globals_start + global->offset);
  }

  byte AddSignature(FunctionSig* sig) {
    module_.signatures.push_back(sig);
    size_t size = module->signatures.size();
    CHECK(size < 127);
    return static_cast<byte>(size - 1);
  }

  template <typename T>
  T* raw_mem_start() {
    DCHECK(instance->mem_start);
    return reinterpret_cast<T*>(instance->mem_start);
  }

  template <typename T>
  T* raw_mem_end() {
    DCHECK(instance->mem_start);
    return reinterpret_cast<T*>(instance->mem_start + instance->mem_size);
  }

  template <typename T>
  T raw_mem_at(int i) {
    DCHECK(instance->mem_start);
    return reinterpret_cast<T*>(instance->mem_start)[i];
  }

  template <typename T>
  T raw_val_at(int i) {
    T val;
    memcpy(&val, reinterpret_cast<void*>(instance->mem_start + i), sizeof(T));
    return val;
  }

  // Zero-initialize the memory.
  void BlankMemory() {
    byte* raw = raw_mem_start<byte>();
    memset(raw, 0, instance->mem_size);
  }

  // Pseudo-randomly intialize the memory.
  void RandomizeMemory(unsigned int seed = 88) {
    byte* raw = raw_mem_start<byte>();
    byte* end = raw_mem_end<byte>();
    v8::base::RandomNumberGenerator rng;
    rng.SetSeed(seed);
    rng.NextBytes(raw, end - raw);
  }

  uint32_t AddFunction(FunctionSig* sig, Handle<Code> code) {
    if (module->functions.size() == 0) {
      // TODO(titzer): Reserving space here to avoid the underlying WasmFunction
      // structs from moving.
      module_.functions.reserve(kMaxFunctions);
    }
    uint32_t index = static_cast<uint32_t>(module->functions.size());
    module_.functions.push_back({sig, index, 0, 0, 0, 0, 0});
    instance->function_code.push_back(code);
    if (interpreter_) {
      const WasmFunction* function = &module->functions.back();
      int interpreter_index = interpreter_->AddFunctionForTesting(function);
      CHECK_EQ(index, static_cast<uint32_t>(interpreter_index));
    }
    DCHECK_LT(index, kMaxFunctions);  // limited for testing.
    return index;
  }

  uint32_t AddJsFunction(FunctionSig* sig, const char* source) {
    Handle<JSFunction> jsfunc = Handle<JSFunction>::cast(v8::Utils::OpenHandle(
        *v8::Local<v8::Function>::Cast(CompileRun(source))));
    uint32_t index = AddFunction(sig, Handle<Code>::null());
    WasmName module_name = ArrayVector("test");
    WasmName function_name;
    Handle<Code> code = CompileWasmToJSWrapper(isolate_, jsfunc, sig,
                                               module_name, function_name);
    instance->function_code[index] = code;
    return index;
  }

  Handle<JSFunction> WrapCode(uint32_t index) {
    // Wrap the code so it can be called as a JS function.
    Handle<String> name = isolate_->factory()->NewStringFromStaticChars("main");
    Handle<JSObject> module_object = Handle<JSObject>(0, isolate_);
    Handle<Code> code = instance->function_code[index];
    WasmJs::InstallWasmFunctionMap(isolate_, isolate_->native_context());
    return compiler::CompileJSToWasmWrapper(isolate_, this, name, code,
                                            module_object, index);
  }

  void SetFunctionCode(uint32_t index, Handle<Code> code) {
    instance->function_code[index] = code;
  }

  void AddIndirectFunctionTable(int* functions, int table_size) {
    Handle<FixedArray> fixed =
        isolate_->factory()->NewFixedArray(2 * table_size);
    instance->function_table = fixed;
    DCHECK_EQ(0u, module->function_table.size());
    for (int i = 0; i < table_size; i++) {
      module_.function_table.push_back(functions[i]);
    }
  }

  void PopulateIndirectFunctionTable() {
    if (instance->function_table.is_null()) return;
    int table_size = static_cast<int>(module->function_table.size());
    for (int i = 0; i < table_size; i++) {
      int function_index = module->function_table[i];
      const WasmFunction* function = &module->functions[function_index];
      instance->function_table->set(i, Smi::FromInt(function->sig_index));
      instance->function_table->set(i + table_size,
                                    *instance->function_code[function_index]);
    }
  }
  WasmFunction* GetFunctionAt(int index) { return &module_.functions[index]; }

  WasmInterpreter* interpreter() { return interpreter_; }
  WasmExecutionMode execution_mode() { return execution_mode_; }

 private:
  WasmExecutionMode execution_mode_;
  WasmModule module_;
  WasmModuleInstance instance_;
  Isolate* isolate_;
  v8::base::AccountingAllocator allocator_;
  uint32_t global_offset;
  V8_ALIGNED(8) byte global_data[kMaxGlobalsSize];  // preallocated global data.
  WasmInterpreter* interpreter_;

  const WasmGlobal* AddGlobal(MachineType mem_type) {
    byte size = WasmOpcodes::MemSize(mem_type);
    global_offset = (global_offset + size - 1) & ~(size - 1);  // align
    module_.globals.push_back({0, 0, mem_type, global_offset, false});
    global_offset += size;
    // limit number of globals.
    CHECK_LT(global_offset, kMaxGlobalsSize);
    return &module->globals.back();
  }
};

inline void TestBuildingGraph(Zone* zone, JSGraph* jsgraph, ModuleEnv* module,
                              FunctionSig* sig,
                              SourcePositionTable* source_position_table,
                              const byte* start, const byte* end) {
  compiler::WasmGraphBuilder builder(zone, jsgraph, sig, source_position_table);
  TreeResult result =
      BuildTFGraph(zone->allocator(), &builder, module, sig, start, end);
  if (result.failed()) {
    ptrdiff_t pc = result.error_pc - result.start;
    ptrdiff_t pt = result.error_pt - result.start;
    std::ostringstream str;
    str << "Verification failed: " << result.error_code << " pc = +" << pc;
    if (result.error_pt) str << ", pt = +" << pt;
    str << ", msg = " << result.error_msg.get();
    FATAL(str.str().c_str());
  }
  builder.Int64LoweringForTesting();
  if (FLAG_trace_turbo_graph) {
    OFStream os(stdout);
    os << AsRPO(*jsgraph->graph());
  }
}

template <typename ReturnType>
class WasmFunctionWrapper : public HandleAndZoneScope,
                            private GraphAndBuilders {
 public:
  WasmFunctionWrapper()
      : GraphAndBuilders(main_zone()),
        inner_code_node_(nullptr),
        signature_(nullptr) {
    // One additional parameter for the pointer to the return value memory.
    Signature<MachineType>::Builder sig_builder(
        zone(), 1, WASM_RUNNER_MAX_NUM_PARAMETERS + 1);

    sig_builder.AddReturn(MachineType::Int32());
    for (int i = 0; i < WASM_RUNNER_MAX_NUM_PARAMETERS + 1; i++) {
      sig_builder.AddParam(MachineType::Pointer());
    }
    signature_ = sig_builder.Build();
  }

  void Init(CallDescriptor* descriptor, MachineType p0 = MachineType::None(),
            MachineType p1 = MachineType::None(),
            MachineType p2 = MachineType::None(),
            MachineType p3 = MachineType::None()) {
    // Create the TF graph for the wrapper. The wrapper always takes four
    // pointers as parameters, but may not pass the values of all pointers to
    // the actual test function.

    // Function, effect, and control.
    Node** parameters =
        zone()->template NewArray<Node*>(WASM_RUNNER_MAX_NUM_PARAMETERS + 3);
    graph()->SetStart(graph()->NewNode(common()->Start(6)));
    Node* effect = graph()->start();
    int parameter_count = 0;

    // Dummy node which gets replaced in SetInnerCode.
    inner_code_node_ = graph()->NewNode(common()->Int32Constant(0));
    parameters[parameter_count++] = inner_code_node_;

    if (p0 != MachineType::None()) {
      parameters[parameter_count] = graph()->NewNode(
          machine()->Load(p0),
          graph()->NewNode(common()->Parameter(0), graph()->start()),
          graph()->NewNode(common()->Int32Constant(0)), effect,
          graph()->start());
      effect = parameters[parameter_count++];
    }
    if (p1 != MachineType::None()) {
      parameters[parameter_count] = graph()->NewNode(
          machine()->Load(p0),
          graph()->NewNode(common()->Parameter(1), graph()->start()),
          graph()->NewNode(common()->Int32Constant(0)), effect,
          graph()->start());
      effect = parameters[parameter_count++];
    }
    if (p2 != MachineType::None()) {
      parameters[parameter_count] = graph()->NewNode(
          machine()->Load(p0),
          graph()->NewNode(common()->Parameter(2), graph()->start()),
          graph()->NewNode(common()->Int32Constant(0)), effect,
          graph()->start());
      effect = parameters[parameter_count++];
    }
    if (p3 != MachineType::None()) {
      parameters[parameter_count] = graph()->NewNode(
          machine()->Load(p0),
          graph()->NewNode(common()->Parameter(3), graph()->start()),
          graph()->NewNode(common()->Int32Constant(0)), effect,
          graph()->start());
      effect = parameters[parameter_count++];
    }

    parameters[parameter_count++] = effect;
    parameters[parameter_count++] = graph()->start();
    Node* call = graph()->NewNode(common()->Call(descriptor), parameter_count,
                                  parameters);

    effect = graph()->NewNode(
        machine()->Store(
            StoreRepresentation(MachineTypeForC<ReturnType>().representation(),
                                WriteBarrierKind::kNoWriteBarrier)),
        graph()->NewNode(common()->Parameter(WASM_RUNNER_MAX_NUM_PARAMETERS),
                         graph()->start()),
        graph()->NewNode(common()->Int32Constant(0)), call, effect,
        graph()->start());
    Node* r = graph()->NewNode(
        common()->Return(),
        graph()->NewNode(common()->Int32Constant(WASM_WRAPPER_RETURN_VALUE)),
        effect, graph()->start());
    graph()->SetEnd(graph()->NewNode(common()->End(2), r, graph()->start()));
  }

  void SetInnerCode(Handle<Code> code_handle) {
    NodeProperties::ChangeOp(inner_code_node_,
                             common()->HeapConstant(code_handle));
  }

  Handle<Code> GetWrapperCode() {
    if (code_.is_null()) {
      Isolate* isolate = CcTest::InitIsolateOnce();

      CallDescriptor* descriptor =
          Linkage::GetSimplifiedCDescriptor(zone(), signature_, true);

      if (kPointerSize == 4) {
        // One additional parameter for the pointer of the return value.
        Signature<MachineRepresentation>::Builder rep_builder(
            zone(), 1, WASM_RUNNER_MAX_NUM_PARAMETERS + 1);

        rep_builder.AddReturn(MachineRepresentation::kWord32);
        for (int i = 0; i < WASM_RUNNER_MAX_NUM_PARAMETERS + 1; i++) {
          rep_builder.AddParam(MachineRepresentation::kWord32);
        }
        Int64Lowering r(graph(), machine(), common(), zone(),
                        rep_builder.Build());
        r.LowerGraph();
      }

      CompilationInfo info(ArrayVector("testing"), isolate, graph()->zone());
      code_ =
          Pipeline::GenerateCodeForTesting(&info, descriptor, graph(), nullptr);
      CHECK(!code_.is_null());
#ifdef ENABLE_DISASSEMBLER
      if (FLAG_print_opt_code) {
        OFStream os(stdout);
        code_->Disassemble("wasm wrapper", os);
      }
#endif
    }

    return code_;
  }

  Signature<MachineType>* signature() const { return signature_; }

 private:
  Node* inner_code_node_;
  Handle<Code> code_;
  Signature<MachineType>* signature_;
};

// A helper for compiling WASM functions for testing. This class can create a
// standalone function if {module} is NULL or a function within a
// {TestingModule}. It contains the internal state for compilation (i.e.
// TurboFan graph) and interpretation (by adding to the interpreter manually).
class WasmFunctionCompiler : public HandleAndZoneScope,
                             private GraphAndBuilders {
 public:
  explicit WasmFunctionCompiler(
      FunctionSig* sig, WasmExecutionMode mode,
      Vector<const char> debug_name = ArrayVector("<WASM UNNAMED>"))
      : GraphAndBuilders(main_zone()),
        execution_mode_(mode),
        jsgraph(this->isolate(), this->graph(), this->common(), nullptr,
                nullptr, this->machine()),
        sig(sig),
        descriptor_(nullptr),
        testing_module_(nullptr),
        debug_name_(debug_name),
        local_decls(main_zone(), sig),
        source_position_table_(this->graph()),
        interpreter_(nullptr) {
    // Create our own function.
    function_ = new WasmFunction();
    function_->sig = sig;
    function_->func_index = 0;
    function_->sig_index = 0;
    if (mode == kExecuteInterpreted) {
      interpreter_ = new WasmInterpreter(nullptr, zone()->allocator());
      int index = interpreter_->AddFunctionForTesting(function_);
      CHECK_EQ(0, index);
    }
  }

  explicit WasmFunctionCompiler(
      FunctionSig* sig, TestingModule* module,
      Vector<const char> debug_name = ArrayVector("<WASM UNNAMED>"))
      : GraphAndBuilders(main_zone()),
        execution_mode_(module->execution_mode()),
        jsgraph(this->isolate(), this->graph(), this->common(), nullptr,
                nullptr, this->machine()),
        sig(sig),
        descriptor_(nullptr),
        testing_module_(module),
        debug_name_(debug_name),
        local_decls(main_zone(), sig),
        source_position_table_(this->graph()),
        interpreter_(module->interpreter()) {
    // Get a new function from the testing module.
    int index = module->AddFunction(sig, Handle<Code>::null());
    function_ = testing_module_->GetFunctionAt(index);
  }

  ~WasmFunctionCompiler() {
    if (testing_module_) return;  // testing module owns the below things.
    delete function_;
    if (interpreter_) delete interpreter_;
  }

  WasmExecutionMode execution_mode_;
  JSGraph jsgraph;
  FunctionSig* sig;
  // The call descriptor is initialized when the function is compiled.
  CallDescriptor* descriptor_;
  TestingModule* testing_module_;
  Vector<const char> debug_name_;
  WasmFunction* function_;
  LocalDeclEncoder local_decls;
  SourcePositionTable source_position_table_;
  WasmInterpreter* interpreter_;

  Isolate* isolate() { return main_isolate(); }
  Graph* graph() const { return main_graph_; }
  Zone* zone() const { return graph()->zone(); }
  CommonOperatorBuilder* common() { return &main_common_; }
  MachineOperatorBuilder* machine() { return &main_machine_; }
  void InitializeDescriptor() {
    if (descriptor_ == nullptr) {
      descriptor_ = testing_module_->GetWasmCallDescriptor(main_zone(), sig);
    }
  }
  CallDescriptor* descriptor() { return descriptor_; }
  uint32_t function_index() { return function_->func_index; }

  void Build(const byte* start, const byte* end) {
    // Build the TurboFan graph.
    local_decls.Prepend(main_zone(), &start, &end);
    TestBuildingGraph(main_zone(), &jsgraph, testing_module_, sig,
                      &source_position_table_, start, end);
    if (interpreter_) {
      // Add the code to the interpreter.
      CHECK(interpreter_->SetFunctionCodeForTesting(function_, start, end));
    }
  }

  byte AllocateLocal(LocalType type) {
    uint32_t index = local_decls.AddLocals(1, type);
    byte result = static_cast<byte>(index);
    DCHECK_EQ(index, result);
    return result;
  }

  Handle<Code> Compile() {
    InitializeDescriptor();
    CallDescriptor* desc = descriptor_;
    if (kPointerSize == 4) {
      desc = testing_module_->GetI32WasmCallDescriptor(this->zone(), desc);
    }
    CompilationInfo info(debug_name_, this->isolate(), this->zone(),
                         Code::ComputeFlags(Code::WASM_FUNCTION));
    v8::base::SmartPointer<CompilationJob> job(Pipeline::NewWasmCompilationJob(
        &info, graph(), desc, &source_position_table_));
    if (job->OptimizeGraph() != CompilationJob::SUCCEEDED ||
        job->GenerateCode() != CompilationJob::SUCCEEDED)
      return Handle<Code>::null();

    Handle<Code> code = info.code();

    // Length is always 2, since usually <wasm_obj, func_index> is stored in
    // the deopt data. Here, we only store the function index.
    DCHECK(code->deoptimization_data() == nullptr ||
           code->deoptimization_data()->length() == 0);
    Handle<FixedArray> deopt_data =
        isolate()->factory()->NewFixedArray(2, TENURED);
    deopt_data->set(1, Smi::FromInt(static_cast<int>(function_index())));
    deopt_data->set_length(2);
    code->set_deoptimization_data(*deopt_data);

#ifdef ENABLE_DISASSEMBLER
    if (FLAG_print_opt_code) {
      OFStream os(stdout);
      code->Disassemble("wasm code", os);
    }
#endif

    return code;
  }

  uint32_t CompileAndAdd(uint16_t sig_index = 0) {
    CHECK(testing_module_);
    function_->sig_index = sig_index;
    Handle<Code> code = Compile();
    testing_module_->SetFunctionCode(function_index(), code);
    return function_index();
  }

  // Set the context, such that e.g. runtime functions can be called.
  void SetModuleContext() {
    if (!testing_module_->instance->context.is_null()) {
      CHECK(testing_module_->instance->context.is_identical_to(
          main_isolate()->native_context()));
      return;
    }
    testing_module_->instance->context = main_isolate()->native_context();
  }
};

// A helper class to build graphs from Wasm bytecode, generate machine
// code, and run that code.
template <typename ReturnType>
class WasmRunner {
 public:
  WasmRunner(WasmExecutionMode execution_mode,
             MachineType p0 = MachineType::None(),
             MachineType p1 = MachineType::None(),
             MachineType p2 = MachineType::None(),
             MachineType p3 = MachineType::None())
      : zone(&allocator_),
        compiled_(false),
        signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1,
                   GetParameterCount(p0, p1, p2, p3), storage_),
        compiler_(&signature_, execution_mode) {
    InitSigStorage(p0, p1, p2, p3);
  }

  WasmRunner(TestingModule* module, MachineType p0 = MachineType::None(),
             MachineType p1 = MachineType::None(),
             MachineType p2 = MachineType::None(),
             MachineType p3 = MachineType::None())
      : zone(&allocator_),
        compiled_(false),
        signature_(MachineTypeForC<ReturnType>() == MachineType::None() ? 0 : 1,
                   GetParameterCount(p0, p1, p2, p3), storage_),
        compiler_(&signature_, module) {
    DCHECK(module);
    InitSigStorage(p0, p1, p2, p3);
  }

  void InitSigStorage(MachineType p0, MachineType p1, MachineType p2,
                      MachineType p3) {
    int index = 0;
    MachineType ret = MachineTypeForC<ReturnType>();
    if (ret != MachineType::None()) {
      storage_[index++] = WasmOpcodes::LocalTypeFor(ret);
    }
    if (p0 != MachineType::None())
      storage_[index++] = WasmOpcodes::LocalTypeFor(p0);
    if (p1 != MachineType::None())
      storage_[index++] = WasmOpcodes::LocalTypeFor(p1);
    if (p2 != MachineType::None())
      storage_[index++] = WasmOpcodes::LocalTypeFor(p2);
    if (p3 != MachineType::None())
      storage_[index++] = WasmOpcodes::LocalTypeFor(p3);

    compiler_.InitializeDescriptor();
    wrapper_.Init(compiler_.descriptor(), p0, p1, p2, p3);
  }

  // Builds a graph from the given Wasm code and generates the machine
  // code and call wrapper for that graph. This method must not be called
  // more than once.
  void Build(const byte* start, const byte* end) {
    CHECK(!compiled_);
    compiled_ = true;
    compiler_.Build(start, end);

    if (!interpret()) {
      // Compile machine code and install it into the module.
      Handle<Code> code = compiler_.Compile();

      if (compiler_.testing_module_) {
        // Update the table of function code in the module.
        compiler_.testing_module_->SetFunctionCode(
            compiler_.function_->func_index, code);
      }

      wrapper_.SetInnerCode(code);
    }
  }

  ReturnType Call() {
    if (interpret()) {
      return CallInterpreter(Vector<WasmVal>(nullptr, 0));
    } else {
      return Call(0, 0, 0, 0);
    }
  }

  template <typename P0>
  ReturnType Call(P0 p0) {
    if (interpret()) {
      WasmVal args[] = {WasmVal(p0)};
      return CallInterpreter(ArrayVector(args));
    } else {
      return Call(p0, 0, 0, 0);
    }
  }

  template <typename P0, typename P1>
  ReturnType Call(P0 p0, P1 p1) {
    if (interpret()) {
      WasmVal args[] = {WasmVal(p0), WasmVal(p1)};
      return CallInterpreter(ArrayVector(args));
    } else {
      return Call(p0, p1, 0, 0);
    }
  }

  template <typename P0, typename P1, typename P2>
  ReturnType Call(P0 p0, P1 p1, P2 p2) {
    if (interpret()) {
      WasmVal args[] = {WasmVal(p0), WasmVal(p1), WasmVal(p2)};
      return CallInterpreter(ArrayVector(args));
    } else {
      return Call(p0, p1, p2, 0);
    }
  }

  template <typename P0, typename P1, typename P2, typename P3>
  ReturnType Call(P0 p0, P1 p1, P2 p2, P3 p3) {
    if (interpret()) {
      WasmVal args[] = {WasmVal(p0), WasmVal(p1), WasmVal(p2), WasmVal(p3)};
      return CallInterpreter(ArrayVector(args));
    } else {
      CodeRunner<int32_t> runner(CcTest::InitIsolateOnce(),
                                 wrapper_.GetWrapperCode(),
                                 wrapper_.signature());
      ReturnType return_value;
      int32_t result = runner.Call<void*, void*, void*, void*, void*>(
          &p0, &p1, &p2, &p3, &return_value);
      CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result);
      return return_value;
    }
  }

  ReturnType CallInterpreter(Vector<WasmVal> args) {
    CHECK_EQ(args.length(),
             static_cast<int>(compiler_.function_->sig->parameter_count()));
    WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
    thread->Reset();
    thread->PushFrame(compiler_.function_, args.start());
    if (thread->Run() == WasmInterpreter::FINISHED) {
      WasmVal val = thread->GetReturnValue();
      return val.to<ReturnType>();
    } else if (thread->state() == WasmInterpreter::TRAPPED) {
      // TODO(titzer): return the correct trap code
      int64_t result = 0xdeadbeefdeadbeef;
      return static_cast<ReturnType>(result);
    } else {
      // TODO(titzer): falling off end
      ReturnType val = 0;
      return val;
    }
  }

  byte AllocateLocal(LocalType type) { return compiler_.AllocateLocal(type); }

  WasmFunction* function() { return compiler_.function_; }
  WasmInterpreter* interpreter() { return compiler_.interpreter_; }

 protected:
  v8::base::AccountingAllocator allocator_;
  Zone zone;
  bool compiled_;
  LocalType storage_[WASM_RUNNER_MAX_NUM_PARAMETERS];
  FunctionSig signature_;
  WasmFunctionCompiler compiler_;
  WasmFunctionWrapper<ReturnType> wrapper_;

  bool interpret() { return compiler_.execution_mode_ == kExecuteInterpreted; }

  static size_t GetParameterCount(MachineType p0, MachineType p1,
                                  MachineType p2, MachineType p3) {
    if (p0 == MachineType::None()) return 0;
    if (p1 == MachineType::None()) return 1;
    if (p2 == MachineType::None()) return 2;
    if (p3 == MachineType::None()) return 3;
    return 4;
  }
};

// A macro to define tests that run in different engine configurations.
// Currently only supports compiled tests, but a future
// RunWasmInterpreted_##name version will allow each test to also run in the
// interpreter.
#define WASM_EXEC_TEST(name)                                               \
  void RunWasm_##name(WasmExecutionMode execution_mode);                   \
  TEST(RunWasmCompiled_##name) { RunWasm_##name(kExecuteCompiled); }       \
  TEST(RunWasmInterpreted_##name) { RunWasm_##name(kExecuteInterpreted); } \
  void RunWasm_##name(WasmExecutionMode execution_mode)

}  // namespace

#endif