text: // 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 <setjmp.h>
  #include <stdint.h>
  #include <stdlib.h>
  #include <string.h>
  #include <array>
  #include <memory>
  
  #include "src/base/utils/random-number-generator.h"
  #include "src/codegen/optimized-compilation-info.h"
  #include "src/compiler/compiler-source-position-table.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-stats.h"
  #include "src/trap-handler/trap-handler.h"
  #include "src/wasm/function-body-decoder.h"
  #include "src/wasm/local-decl-encoder.h"
  #include "src/wasm/wasm-code-manager.h"
  #include "src/wasm/wasm-external-refs.h"
  #include "src/wasm/wasm-interpreter.h"
  #include "src/wasm/wasm-js.h"
  #include "src/wasm/wasm-module.h"
  #include "src/wasm/wasm-objects-inl.h"
  #include "src/wasm/wasm-objects.h"
  #include "src/wasm/wasm-opcodes.h"
  #include "src/wasm/wasm-tier.h"
  #include "src/zone/accounting-allocator.h"
  #include "src/zone/zone.h"
  
  #include "test/cctest/cctest.h"
  #include "test/cctest/compiler/call-tester.h"
  #include "test/cctest/compiler/graph-and-builders.h"
  #include "test/cctest/compiler/value-helper.h"
  #include "test/common/wasm/flag-utils.h"
  
  namespace v8 {
  namespace internal {
  namespace wasm {
  
  using base::ReadLittleEndianValue;
  using base::WriteLittleEndianValue;
  
  constexpr uint32_t kMaxFunctions = 10;
  constexpr uint32_t kMaxGlobalsSize = 128;
  
  using compiler::CallDescriptor;
  using compiler::MachineTypeForC;
  using compiler::Node;
  
  // 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_WRAPPER_RETURN_VALUE 8754
  
  #define BUILD(r, ...)                      \
    do {                                     \
      byte code[] = {__VA_ARGS__};           \
      r.Build(code, code + arraysize(code)); \
    } while (false)
  
  template <typename T>
  void AppendSingle(std::vector<byte>* code, T t) {
    static_assert(std::is_integral<T>::value,
                  "Special types need specializations");
    code->push_back(t);
  }
  
  // Specialized for WasmOpcode.
  template <>
  void AppendSingle<WasmOpcode>(std::vector<byte>* code, WasmOpcode op);
  
  template <typename... T>
  void Append(std::vector<byte>* code, T... ts) {
    static_assert(sizeof...(ts) == 0, "Base case for appending bytes to code.");
  }
  
  template <typename First, typename... Rest>
  void Append(std::vector<byte>* code, First first, Rest... rest) {
    AppendSingle(code, first);
    Append(code, rest...);
  }
  
  // Like BUILD but pushes code bytes into a std::vector instead of an array
  // initializer. This is useful for opcodes (like SIMD), that are LEB128
  // (variable-sized). We use recursive template instantiations with variadic
  // template arguments, so that the Append calls can handle either bytes or
  // opcodes. AppendSingle is specialized for WasmOpcode, and appends multiple
  // bytes. This allows existing callers to swap out the BUILD macro for BUILD_V
  // macro without changes. Also see https://crbug.com/v8/10258.
  #define BUILD_V(r, ...)                              \
    do {                                               \
      std::vector<byte> code;                          \
      Append(&code, __VA_ARGS__);                      \
      r.Build(code.data(), code.data() + code.size()); \
    } while (false)
  
  // For tests that must manually import a JSFunction with source code.
  struct ManuallyImportedJSFunction {
    const FunctionSig* sig;
    Handle<JSFunction> js_function;
  };
  
  // A  Wasm module builder. Globals are pre-set, however, memory and code may be
  // progressively added by a test. In turn, we piecemeal update the runtime
  // objects, i.e. {WasmInstanceObject}, {WasmModuleObject} and, if necessary,
  // the interpreter.
  class TestingModuleBuilder {
   public:
    TestingModuleBuilder(Zone*, ManuallyImportedJSFunction*, ExecutionTier,
                         RuntimeExceptionSupport, LowerSimd);
    ~TestingModuleBuilder();
  
    void ChangeOriginToAsmjs() { test_module_->origin = kAsmJsSloppyOrigin; }
  
    byte* AddMemory(uint32_t size, SharedFlag shared = SharedFlag::kNotShared);
  
    size_t CodeTableLength() const { return native_module_->num_functions(); }
  
    template <typename T>
    T* AddMemoryElems(uint32_t count) {
      AddMemory(count * sizeof(T));
      return raw_mem_start<T>();
    }
  
    template <typename T>
    T* AddGlobal(ValueType type = ValueType::For(MachineTypeForC<T>())) {
      const WasmGlobal* global = AddGlobal(type);
      return reinterpret_cast<T*>(globals_data_ + global->offset);
    }
  
    byte AddSignature(const FunctionSig* sig) {
      DCHECK_EQ(test_module_->signatures.size(),
                test_module_->signature_ids.size());
      test_module_->signatures.push_back(sig);
      auto canonical_sig_num = test_module_->signature_map.FindOrInsert(*sig);
      test_module_->signature_ids.push_back(canonical_sig_num);
      size_t size = test_module_->signatures.size();
      CHECK_GT(127, size);
      return static_cast<byte>(size - 1);
    }
  
    uint32_t mem_size() { return mem_size_; }
  
    template <typename T>
    T* raw_mem_start() {
      DCHECK(mem_start_);
      return reinterpret_cast<T*>(mem_start_);
    }
  
    template <typename T>
    T* raw_mem_end() {
      DCHECK(mem_start_);
      return reinterpret_cast<T*>(mem_start_ + mem_size_);
    }
  
    template <typename T>
    T raw_mem_at(int i) {
      DCHECK(mem_start_);
      return ReadMemory(&(reinterpret_cast<T*>(mem_start_)[i]));
    }
  
    template <typename T>
    T raw_val_at(int i) {
      return ReadMemory(reinterpret_cast<T*>(mem_start_ + i));
    }
  
    template <typename T>
    void WriteMemory(T* p, T val) {
      WriteLittleEndianValue<T>(reinterpret_cast<Address>(p), val);
    }
  
    template <typename T>
    T ReadMemory(T* p) {
      return ReadLittleEndianValue<T>(reinterpret_cast<Address>(p));
    }
  
    // Zero-initialize the memory.
    void BlankMemory() {
      byte* raw = raw_mem_start<byte>();
      memset(raw, 0, 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);
    }
  
    void SetMaxMemPages(uint32_t maximum_pages) {
      test_module_->maximum_pages = maximum_pages;
      if (instance_object()->has_memory_object()) {
        instance_object()->memory_object().set_maximum_pages(maximum_pages);
      }
    }
  
    void SetHasSharedMemory() { test_module_->has_shared_memory = true; }
  
    enum FunctionType { kImport, kWasm };
    uint32_t AddFunction(const FunctionSig* sig, const char* name,
                         FunctionType type);
  
    // Freezes the signature map of the module and allocates the storage for
    // export wrappers.
    void FreezeSignatureMapAndInitializeWrapperCache();
  
    // Wrap the code so it can be called as a JS function.
    Handle<JSFunction> WrapCode(uint32_t index);
  
    // If function_indexes is {nullptr}, the contents of the table will be
    // initialized with null functions.
    void AddIndirectFunctionTable(const uint16_t* function_indexes,
                                  uint32_t table_size);
  
    uint32_t AddBytes(Vector<const byte> bytes);
  
    uint32_t AddException(const FunctionSig* sig);
  
    uint32_t AddPassiveDataSegment(Vector<const byte> bytes);
    uint32_t AddPassiveElementSegment(const std::vector<uint32_t>& entries);
  
    WasmFunction* GetFunctionAt(int index) {
      return &test_module_->functions[index];
    }
  
    WasmInterpreter* interpreter() const { return interpreter_; }
    bool interpret() const { return interpreter_ != nullptr; }
    LowerSimd lower_simd() const { return lower_simd_; }
    Isolate* isolate() const { return isolate_; }
    Handle<WasmInstanceObject> instance_object() const {
      return instance_object_;
    }
    WasmCode* GetFunctionCode(uint32_t index) const {
      return native_module_->GetCode(index);
    }
    Address globals_start() const {
      return reinterpret_cast<Address>(globals_data_);
    }
  
    void SetExecutable() { native_module_->SetExecutable(true); }
  
    void TierDown() { native_module_->TierDown(isolate_); }
  
    enum AssumeDebugging : bool { kDebug = true, kNoDebug = false };
    CompilationEnv CreateCompilationEnv(AssumeDebugging = kNoDebug);
  
    ExecutionTier execution_tier() const { return execution_tier_; }
  
    RuntimeExceptionSupport runtime_exception_support() const {
      return runtime_exception_support_;
    }
  
   private:
    std::shared_ptr<WasmModule> test_module_;
    WasmModule* test_module_ptr_;
    Isolate* isolate_;
    WasmFeatures enabled_features_;
    uint32_t global_offset = 0;
    byte* mem_start_ = nullptr;
    uint32_t mem_size_ = 0;
    alignas(16) byte globals_data_[kMaxGlobalsSize];
    WasmInterpreter* interpreter_ = nullptr;
    ExecutionTier execution_tier_;
    Handle<WasmInstanceObject> instance_object_;
    NativeModule* native_module_ = nullptr;
    RuntimeExceptionSupport runtime_exception_support_;
    LowerSimd lower_simd_;
  
    // Data segment arrays that are normally allocated on the instance.
    std::vector<byte> data_segment_data_;
    std::vector<Address> data_segment_starts_;
    std::vector<uint32_t> data_segment_sizes_;
    std::vector<byte> dropped_elem_segments_;
  
    const WasmGlobal* AddGlobal(ValueType type);
  
    Handle<WasmInstanceObject> InitInstanceObject();
  };
  
  void TestBuildingGraph(Zone* zone, compiler::JSGraph* jsgraph,
                         CompilationEnv* module, const FunctionSig* sig,
                         compiler::SourcePositionTable* source_position_table,
                         const byte* start, const byte* end);
  
  class WasmFunctionWrapper : private compiler::GraphAndBuilders {
   public:
    WasmFunctionWrapper(Zone* zone, int num_params);
  
    void Init(CallDescriptor* call_descriptor, MachineType return_type,
              Vector<MachineType> param_types);
  
    template <typename ReturnType, typename... ParamTypes>
    void Init(CallDescriptor* call_descriptor) {
      std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
          {MachineTypeForC<ParamTypes>()...}};
      Vector<MachineType> param_vec(param_machine_types.data(),
                                    param_machine_types.size());
      Init(call_descriptor, MachineTypeForC<ReturnType>(), param_vec);
    }
  
    void SetInnerCode(WasmCode* code) {
      intptr_t address = static_cast<intptr_t>(code->instruction_start());
      compiler::NodeProperties::ChangeOp(
          inner_code_node_,
          common()->ExternalConstant(ExternalReference::FromRawAddress(address)));
    }
  
    const compiler::Operator* IntPtrConstant(intptr_t value) {
      return machine()->Is32()
                 ? common()->Int32Constant(static_cast<int32_t>(value))
                 : common()->Int64Constant(static_cast<int64_t>(value));
    }
  
    void SetInstance(Handle<WasmInstanceObject> instance) {
      compiler::NodeProperties::ChangeOp(context_address_,
                                         common()->HeapConstant(instance));
    }
  
    Handle<Code> GetWrapperCode();
  
    Signature<MachineType>* signature() const { return signature_; }
  
   private:
    Node* inner_code_node_;
    Node* context_address_;
    MaybeHandle<Code> code_;
    Signature<MachineType>* signature_;
  };
  
  // A helper for compiling wasm functions for testing.
  // It contains the internal state for compilation (i.e. TurboFan graph) and
  // interpretation (by adding to the interpreter manually).
  class WasmFunctionCompiler : public compiler::GraphAndBuilders {
   public:
    ~WasmFunctionCompiler();
  
    Isolate* isolate() { return builder_->isolate(); }
    CallDescriptor* descriptor() {
      if (descriptor_ == nullptr) {
        descriptor_ = compiler::GetWasmCallDescriptor(zone(), sig);
      }
      return descriptor_;
    }
    uint32_t function_index() { return function_->func_index; }
  
    void Build(const byte* start, const byte* end);
  
    byte AllocateLocal(ValueType type) {
      uint32_t index = local_decls.AddLocals(1, type);
      byte result = static_cast<byte>(index);
      DCHECK_EQ(index, result);
      return result;
    }
  
    void SetSigIndex(int sig_index) { function_->sig_index = sig_index; }
  
   private:
    friend class WasmRunnerBase;
  
    WasmFunctionCompiler(Zone* zone, const FunctionSig* sig,
                         TestingModuleBuilder* builder, const char* name);
  
    compiler::JSGraph jsgraph;
    const FunctionSig* sig;
    // The call descriptor is initialized when the function is compiled.
    CallDescriptor* descriptor_;
    TestingModuleBuilder* builder_;
    WasmFunction* function_;
    LocalDeclEncoder local_decls;
    compiler::SourcePositionTable source_position_table_;
    WasmInterpreter* interpreter_;
  };
  
  // A helper class to build a module around Wasm bytecode, generate machine
  // code, and run that code.
  class WasmRunnerBase : public HandleAndZoneScope {
   public:
    WasmRunnerBase(ManuallyImportedJSFunction* maybe_import,
                   ExecutionTier execution_tier, int num_params,
                   RuntimeExceptionSupport runtime_exception_support,
                   LowerSimd lower_simd)
        : zone_(&allocator_, ZONE_NAME),
          builder_(&zone_, maybe_import, execution_tier,
                   runtime_exception_support, lower_simd),
          wrapper_(&zone_, num_params) {}
  
    // 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;
      functions_[0]->Build(start, end);
    }
  
    // Resets the state for building the next function.
    // The main function called will always be the first function.
    template <typename ReturnType, typename... ParamTypes>
    WasmFunctionCompiler& NewFunction(const char* name = nullptr) {
      return NewFunction(CreateSig<ReturnType, ParamTypes...>(), name);
    }
  
    // Resets the state for building the next function.
    // The main function called will be the last generated function.
    // Returns the index of the previously built function.
    WasmFunctionCompiler& NewFunction(const FunctionSig* sig,
                                      const char* name = nullptr) {
      functions_.emplace_back(
          new WasmFunctionCompiler(&zone_, sig, &builder_, name));
      builder().AddSignature(sig);
      return *functions_.back();
    }
  
    byte AllocateLocal(ValueType type) {
      return functions_[0]->AllocateLocal(type);
    }
  
    uint32_t function_index() { return functions_[0]->function_index(); }
    WasmFunction* function() { return functions_[0]->function_; }
    WasmInterpreter* interpreter() {
      DCHECK(interpret());
      return functions_[0]->interpreter_;
    }
    bool possible_nondeterminism() { return possible_nondeterminism_; }
    TestingModuleBuilder& builder() { return builder_; }
    Zone* zone() { return &zone_; }
  
    bool interpret() { return builder_.interpret(); }
  
    void TierDown() { builder_.TierDown(); }
  
    template <typename ReturnType, typename... ParamTypes>
    FunctionSig* CreateSig() {
      return WasmRunnerBase::CreateSig<ReturnType, ParamTypes...>(&zone_);
    }
  
    template <typename ReturnType, typename... ParamTypes>
    static FunctionSig* CreateSig(Zone* zone) {
      std::array<MachineType, sizeof...(ParamTypes)> param_machine_types{
          {MachineTypeForC<ParamTypes>()...}};
      Vector<MachineType> param_vec(param_machine_types.data(),
                                    param_machine_types.size());
      return CreateSig(zone, MachineTypeForC<ReturnType>(), param_vec);
    }
  
   private:
    static FunctionSig* CreateSig(Zone* zone, MachineType return_type,
                                  Vector<MachineType> param_types);
  
   protected:
    v8::internal::AccountingAllocator allocator_;
    Zone zone_;
    TestingModuleBuilder builder_;
    std::vector<std::unique_ptr<WasmFunctionCompiler>> functions_;
    WasmFunctionWrapper wrapper_;
    bool compiled_ = false;
    bool possible_nondeterminism_ = false;
    int32_t main_fn_index_ = 0;
  
   public:
    // This field has to be static. Otherwise, gcc complains about the use in
    // the lambda context below.
    static bool trap_happened;
  };
  
  template <typename ReturnType, typename... ParamTypes>
  class WasmRunner : public WasmRunnerBase {
   public:
    WasmRunner(ExecutionTier execution_tier,
               ManuallyImportedJSFunction* maybe_import = nullptr,
               const char* main_fn_name = "main",
               RuntimeExceptionSupport runtime_exception_support =
                   kNoRuntimeExceptionSupport,
               LowerSimd lower_simd = kNoLowerSimd)
        : WasmRunnerBase(maybe_import, execution_tier, sizeof...(ParamTypes),
                         runtime_exception_support, lower_simd) {
      WasmFunctionCompiler& main_fn =
          NewFunction<ReturnType, ParamTypes...>(main_fn_name);
      // Non-zero if there is an import.
      main_fn_index_ = main_fn.function_index();
  
      if (!interpret()) {
        wrapper_.Init<ReturnType, ParamTypes...>(main_fn.descriptor());
      }
    }
  
    WasmRunner(ExecutionTier execution_tier, LowerSimd lower_simd)
        : WasmRunner(execution_tier, nullptr, "main", kNoRuntimeExceptionSupport,
                     lower_simd) {}
  
    void SetUpTrapCallback() {
      WasmRunnerBase::trap_happened = false;
      auto trap_callback = []() -> void {
        WasmRunnerBase::trap_happened = true;
        set_trap_callback_for_testing(nullptr);
      };
      set_trap_callback_for_testing(trap_callback);
    }
  
    ReturnType Call(ParamTypes... p) {
      DCHECK(compiled_);
      if (interpret()) return CallInterpreter(p...);
  
      ReturnType return_value = static_cast<ReturnType>(0xDEADBEEFDEADBEEF);
      SetUpTrapCallback();
  
      wrapper_.SetInnerCode(builder_.GetFunctionCode(main_fn_index_));
      wrapper_.SetInstance(builder_.instance_object());
      builder_.SetExecutable();
      Handle<Code> wrapper_code = wrapper_.GetWrapperCode();
      compiler::CodeRunner<int32_t> runner(CcTest::InitIsolateOnce(),
                                           wrapper_code, wrapper_.signature());
      int32_t result;
      {
        SetThreadInWasmFlag();
  
        result = runner.Call(static_cast<void*>(&p)...,
                             static_cast<void*>(&return_value));
  
        ClearThreadInWasmFlag();
      }
      CHECK_EQ(WASM_WRAPPER_RETURN_VALUE, result);
      return WasmRunnerBase::trap_happened
                 ? static_cast<ReturnType>(0xDEADBEEFDEADBEEF)
                 : return_value;
    }
  
    ReturnType CallInterpreter(ParamTypes... p) {
      WasmInterpreter::Thread* thread = interpreter()->GetThread(0);
      thread->Reset();
      std::array<WasmValue, sizeof...(p)> args{{WasmValue(p)...}};
      thread->InitFrame(function(), args.data());
      thread->Run();
      CHECK_GT(thread->NumInterpretedCalls(), 0);
      if (thread->state() == WasmInterpreter::FINISHED) {
        WasmValue val = thread->GetReturnValue();
        possible_nondeterminism_ |= thread->PossibleNondeterminism();
        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
        return ReturnType{0};
      }
    }
  
    void CheckCallApplyViaJS(double expected, uint32_t function_index,
                             Handle<Object>* buffer, int count) {
      Isolate* isolate = builder_.isolate();
      SetUpTrapCallback();
      if (jsfuncs_.size() <= function_index) {
        jsfuncs_.resize(function_index + 1);
      }
      if (jsfuncs_[function_index].is_null()) {
        jsfuncs_[function_index] = builder_.WrapCode(function_index);
      }
      Handle<JSFunction> jsfunc = jsfuncs_[function_index];
      Handle<Object> global(isolate->context().global_object(), isolate);
      MaybeHandle<Object> retval =
          Execution::TryCall(isolate, jsfunc, global, count, buffer,
                             Execution::MessageHandling::kReport, nullptr);
  
      if (retval.is_null() || WasmRunnerBase::trap_happened) {
        CHECK_EQ(expected, static_cast<double>(0xDEADBEEF));
      } else {
        Handle<Object> result = retval.ToHandleChecked();
        if (result->IsSmi()) {
          CHECK_EQ(expected, Smi::ToInt(*result));
        } else {
          CHECK(result->IsHeapNumber());
          CHECK_DOUBLE_EQ(expected, HeapNumber::cast(*result).value());
        }
      }
  
      if (builder_.interpret()) {
        CHECK_GT(builder_.interpreter()->GetThread(0)->NumInterpretedCalls(), 0);
      }
    }
  
    void CheckCallViaJS(double expected, ParamTypes... p) {
      Isolate* isolate = builder_.isolate();
      // MSVC doesn't allow empty arrays, so include a dummy at the end.
      Handle<Object> buffer[] = {isolate->factory()->NewNumber(p)...,
                                 Handle<Object>()};
      CheckCallApplyViaJS(expected, function()->func_index, buffer, sizeof...(p));
    }
  
    void CheckCallViaJSTraps(ParamTypes... p) {
      CheckCallViaJS(static_cast<double>(0xDEADBEEF), p...);
    }
  
    void CheckUsedExecutionTier(ExecutionTier expected_tier) {
      // Liftoff can fail and fallback to Turbofan, so check that the function
      // gets compiled by the tier requested, to guard against accidental success.
      CHECK(compiled_);
      CHECK_EQ(expected_tier, builder_.GetFunctionCode(0)->tier());
    }
  
    Handle<Code> GetWrapperCode() { return wrapper_.GetWrapperCode(); }
  
   private:
    wasm::WasmCodeRefScope code_ref_scope_;
    std::vector<Handle<JSFunction>> jsfuncs_;
  
    void SetThreadInWasmFlag() {
      *reinterpret_cast<int*>(trap_handler::GetThreadInWasmThreadLocalAddress()) =
          true;
    }
  
    void ClearThreadInWasmFlag() {
      *reinterpret_cast<int*>(trap_handler::GetThreadInWasmThreadLocalAddress()) =
          false;
    }
  };
  
  // A macro to define tests that run in different engine configurations.
  #define WASM_EXEC_TEST(name)                                                 \
    void RunWasm_##name(ExecutionTier execution_tier);                         \
    TEST(RunWasmTurbofan_##name) { RunWasm_##name(ExecutionTier::kTurbofan); } \
    TEST(RunWasmLiftoff_##name) { RunWasm_##name(ExecutionTier::kLiftoff); }   \
    TEST(RunWasmInterpreter_##name) {                                          \
      RunWasm_##name(ExecutionTier::kInterpreter);                             \
    }                                                                          \
    void RunWasm_##name(ExecutionTier execution_tier)
  
  #define WASM_COMPILED_EXEC_TEST(name)                                        \
    void RunWasm_##name(ExecutionTier execution_tier);                         \
    TEST(RunWasmTurbofan_##name) { RunWasm_##name(ExecutionTier::kTurbofan); } \
    TEST(RunWasmLiftoff_##name) { RunWasm_##name(ExecutionTier::kLiftoff); }   \
    void RunWasm_##name(ExecutionTier execution_tier)
  
  }  // namespace wasm
  }  // namespace internal
  }  // namespace v8
  
  #endif
  