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

// This implementation is originally from
// https://github.com/WebAssembly/wasm-c-api/:

// Copyright 2019 Andreas Rossberg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/wasm/c-api.h"

#include <cstring>
#include <iomanip>
#include <iostream>

#include "include/libplatform/libplatform.h"
#include "include/v8-initialization.h"
#include "src/api/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/compiler/wasm-compiler.h"
#include "src/flags/flags.h"
#include "src/objects/call-site-info-inl.h"
#include "src/objects/js-collection-inl.h"
#include "src/objects/managed-inl.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/signature-hashing.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-result.h"
#include "src/wasm/wasm-serialization.h"
#include "third_party/wasm-api/wasm.h"

#ifdef V8_OS_WIN

// Setup for Windows DLL export/import. When building the V8 DLL the
// BUILDING_V8_SHARED needs to be defined. When building a program which uses
// the V8 DLL USING_V8_SHARED needs to be defined. When either building the V8
// static library or building a program which uses the V8 static library neither
// BUILDING_V8_SHARED nor USING_V8_SHARED should be defined.
#if !defined(LIBWASM_STATIC) && defined(USING_V8_SHARED)
#define WASM_EXPORT __declspec(dllimport)
#elif !defined(LIBWASM_STATIC)
#define WASM_EXPORT __declspec(dllexport)
#else
#define WASM_EXPORT
#endif  // BUILDING_V8_SHARED

#else  // V8_OS_WIN

// Setup for Linux shared library export.
#if V8_HAS_ATTRIBUTE_VISIBILITY && \
    (defined(BUILDING_V8_SHARED) || defined(USING_V8_SHARED))
#define WASM_EXPORT __attribute__((visibility("default")))
#else
#define WASM_EXPORT
#endif  // V8_HAS_ATTRIBUTE_VISIBILITY && ...

#endif  // V8_OS_WIN

#ifdef ENABLE_VTUNE_JIT_INTERFACE
#include "third_party/vtune/v8-vtune.h"
#endif

#ifdef WASM_API_DEBUG
#error "WASM_API_DEBUG is unsupported"
#endif

// If you want counters support (what --dump-counters does for the d8 shell),
// then set this to 1 (in here, or via -DDUMP_COUNTERS=1 compiler argument).
#define DUMP_COUNTERS 0

namespace wasm {

namespace {

// Multi-cage pointer compression mode related note.
// Wasm C-Api is allowed to be used from a thread that's not bound to any
// Isolate. As a result, in a multi-cage pointer compression mode it's not
// guaranteed that current pointer compression cage base value is initialized
// for current thread (see V8HeapCompressionScheme::base_) which makes it
// impossible to read compressed pointers from V8 heap objects.
// This scope ensures that the pointer compression base value is set according
// to respective Wasm C-Api object.
// For all other configurations this scope is a no-op.
using PtrComprCageAccessScope = i::PtrComprCageAccessScope;

auto ReadLebU64(const byte_t** pos) -> uint64_t {
  uint64_t n = 0;
  uint64_t shift = 0;
  byte_t b;
  do {
    b = **pos;
    (*pos)++;
    n += (b & 0x7f) << shift;
    shift += 7;
  } while ((b & 0x80) != 0);
  return n;
}

template <typename T>
ValKind V8ValueTypeToWasm(T v8_valtype)
  requires(std::is_same_v<T, i::wasm::ValueType> ||
           std::is_same_v<T, i::wasm::CanonicalValueType>)
{
  switch (v8_valtype.kind()) {
    case i::wasm::kI32:
      return ValKind::I32;
    case i::wasm::kI64:
      return ValKind::I64;
    case i::wasm::kF32:
      return ValKind::F32;
    case i::wasm::kF64:
      return ValKind::F64;
    case i::wasm::kRef:
    case i::wasm::kRefNull:
      switch (v8_valtype.generic_kind()) {
        case i::wasm::GenericKind::kFunc:
          return ValKind::FUNCREF;
        case i::wasm::GenericKind::kExtern:
          return ValKind::EXTERNREF;
        default:
          UNREACHABLE();
      }
    default:
      UNREACHABLE();
  }
}

i::wasm::ValueType WasmValKindToV8(ValKind kind) {
  switch (kind) {
    case ValKind::I32:
      return i::wasm::kWasmI32;
    case ValKind::I64:
      return i::wasm::kWasmI64;
    case ValKind::F32:
      return i::wasm::kWasmF32;
    case ValKind::F64:
      return i::wasm::kWasmF64;
    case ValKind::FUNCREF:
      return i::wasm::kWasmFuncRef;
    case ValKind::EXTERNREF:
      return i::wasm::kWasmExternRef;
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
}

Name GetNameFromWireBytes(const i::wasm::WireBytesRef& ref,
                          v8::base::Vector<const uint8_t> wire_bytes) {
  DCHECK_LE(ref.offset(), wire_bytes.size());
  DCHECK_LE(ref.end_offset(), wire_bytes.size());
  if (ref.length() == 0) return Name::make();
  Name name = Name::make_uninitialized(ref.length());
  std::memcpy(name.get(), wire_bytes.begin() + ref.offset(), ref.length());
  return name;
}

own<FuncType> FunctionSigToFuncType(const i::wasm::FunctionSig* sig) {
  size_t param_count = sig->parameter_count();
  ownvec<ValType> params = ownvec<ValType>::make_uninitialized(param_count);
  for (size_t i = 0; i < param_count; i++) {
    params[i] = ValType::make(V8ValueTypeToWasm(sig->GetParam(i)));
  }
  size_t return_count = sig->return_count();
  ownvec<ValType> results = ownvec<ValType>::make_uninitialized(return_count);
  for (size_t i = 0; i < return_count; i++) {
    results[i] = ValType::make(V8ValueTypeToWasm(sig->GetReturn(i)));
  }
  return FuncType::make(std::move(params), std::move(results));
}

own<ExternType> GetImportExportType(const i::wasm::WasmModule* module,
                                    const i::wasm::ImportExportKindCode kind,
                                    const uint32_t index) {
  switch (kind) {
    case i::wasm::kExternalFunction:
    case i::wasm::kExternalExactFunction: {
      return FunctionSigToFuncType(module->functions[index].sig);
    }
    case i::wasm::kExternalTable: {
      const i::wasm::WasmTable& table = module->tables[index];
      own<ValType> elem = ValType::make(V8ValueTypeToWasm(table.type));
      Limits limits(table.initial_size,
                    table.has_maximum_size
                        ? v8::base::checked_cast<int32_t>(table.maximum_size)
                        : -1);
      return TableType::make(std::move(elem), limits);
    }
    case i::wasm::kExternalMemory: {
      const i::wasm::WasmMemory& memory = module->memories[index];
      Limits limits(memory.initial_pages,
                    memory.has_maximum_pages
                        ? v8::base::checked_cast<int32_t>(memory.maximum_pages)
                        : -1);
      return MemoryType::make(limits);
    }
    case i::wasm::kExternalGlobal: {
      const i::wasm::WasmGlobal& global = module->globals[index];
      own<ValType> content = ValType::make(V8ValueTypeToWasm(global.type));
      Mutability mutability =
          global.mutability ? Mutability::VAR : Mutability::CONST;
      return GlobalType::make(std::move(content), mutability);
    }
    case i::wasm::kExternalTag:
      UNREACHABLE();
  }
}

}  // namespace

/// BEGIN FILE wasm-v8.cc

///////////////////////////////////////////////////////////////////////////////
// Auxiliaries

[[noreturn]] void WASM_UNIMPLEMENTED(const char* s) {
  std::cerr << "Wasm API: " << s << " not supported yet!\n";
  exit(1);
}

template <class T>
void ignore(T) {}

template <class C>
struct implement;

template <class C>
auto impl(C* x) -> typename implement<C>::type* {
  return reinterpret_cast<typename implement<C>::type*>(x);
}

template <class C>
auto impl(const C* x) -> const typename implement<C>::type* {
  return reinterpret_cast<const typename implement<C>::type*>(x);
}

template <class C>
auto seal(typename implement<C>::type* x) -> C* {
  return reinterpret_cast<C*>(x);
}

template <class C>
auto seal(const typename implement<C>::type* x) -> const C* {
  return reinterpret_cast<const C*>(x);
}

///////////////////////////////////////////////////////////////////////////////
// Runtime Environment

// Configuration

struct ConfigImpl {};

template <>
struct implement<Config> {
  using type = ConfigImpl;
};

WASM_EXPORT void Config::destroy() { delete impl(this); }

WASM_EXPORT auto Config::make() -> own<Config> {
  return own<Config>(seal<Config>(new (std::nothrow) ConfigImpl()));
}

// Engine

#if DUMP_COUNTERS
class Counter {
 public:
  static const int kMaxNameSize = 64;
  int32_t* Bind(const char* name, bool is_histogram) {
    int i;
    for (i = 0; i < kMaxNameSize - 1 && name[i]; i++) {
      name_[i] = static_cast<char>(name[i]);
    }
    name_[i] = '\0';
    is_histogram_ = is_histogram;
    return ptr();
  }
  int32_t* ptr() { return &count_; }
  int32_t count() { return count_; }
  int32_t sample_total() { return sample_total_; }
  bool is_histogram() { return is_histogram_; }
  void AddSample(int32_t sample) {
    count_++;
    sample_total_ += sample;
  }

 private:
  int32_t count_;
  int32_t sample_total_;
  bool is_histogram_;
  uint8_t name_[kMaxNameSize];
};

class CounterCollection {
 public:
  CounterCollection() = default;
  Counter* GetNextCounter() {
    if (counters_in_use_ == kMaxCounters) return nullptr;
    return &counters_[counters_in_use_++];
  }

 private:
  static const unsigned kMaxCounters = 512;
  uint32_t counters_in_use_{0};
  Counter counters_[kMaxCounters];
};

using CounterMap = std::unordered_map<std::string, Counter*>;

#endif

struct EngineImpl {
  static bool created;

  std::unique_ptr<v8::Platform> platform;

#if DUMP_COUNTERS
  static CounterCollection counters_;
  static CounterMap* counter_map_;

  static Counter* GetCounter(const char* name, bool is_histogram) {
    auto map_entry = counter_map_->find(name);
    Counter* counter =
        map_entry != counter_map_->end() ? map_entry->second : nullptr;

    if (counter == nullptr) {
      counter = counters_.GetNextCounter();
      if (counter != nullptr) {
        (*counter_map_)[name] = counter;
        counter->Bind(name, is_histogram);
      }
    } else {
      DCHECK(counter->is_histogram() == is_histogram);
    }
    return counter;
  }

  static int* LookupCounter(const char* name) {
    Counter* counter = GetCounter(name, false);

    if (counter != nullptr) {
      return counter->ptr();
    } else {
      return nullptr;
    }
  }

  static void* CreateHistogram(const char* name, int min, int max,
                               size_t buckets) {
    return GetCounter(name, true);
  }

  static void AddHistogramSample(void* histogram, int sample) {
    Counter* counter = reinterpret_cast<Counter*>(histogram);
    counter->AddSample(sample);
  }
#endif

  EngineImpl() {
    assert(!created);
    created = true;
#if DUMP_COUNTERS
    counter_map_ = new CounterMap();
#endif
  }

  ~EngineImpl() {
#if DUMP_COUNTERS
    std::vector<std::pair<std::string, Counter*>> counters(
        counter_map_->begin(), counter_map_->end());
    std::sort(counters.begin(), counters.end());
    // Dump counters in formatted boxes.
    constexpr int kNameBoxSize = 64;
    constexpr int kValueBoxSize = 13;
    std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
              << std::string(kValueBoxSize, '-') << "+\n";
    std::cout << "| Name" << std::string(kNameBoxSize - 5, ' ') << "| Value"
              << std::string(kValueBoxSize - 6, ' ') << "|\n";
    std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
              << std::string(kValueBoxSize, '-') << "+\n";
    for (const auto& pair : counters) {
      std::string key = pair.first;
      Counter* counter = pair.second;
      if (counter->is_histogram()) {
        std::cout << "| c:" << std::setw(kNameBoxSize - 4) << std::left << key
                  << " | " << std::setw(kValueBoxSize - 2) << std::right
                  << counter->count() << " |\n";
        std::cout << "| t:" << std::setw(kNameBoxSize - 4) << std::left << key
                  << " | " << std::setw(kValueBoxSize - 2) << std::right
                  << counter->sample_total() << " |\n";
      } else {
        std::cout << "| " << std::setw(kNameBoxSize - 2) << std::left << key
                  << " | " << std::setw(kValueBoxSize - 2) << std::right
                  << counter->count() << " |\n";
      }
    }
    std::cout << "+" << std::string(kNameBoxSize, '-') << "+"
              << std::string(kValueBoxSize, '-') << "+\n";
    delete counter_map_;
#endif
    v8::V8::Dispose();
    v8::V8::DisposePlatform();
  }
};

bool EngineImpl::created = false;

#if DUMP_COUNTERS
CounterCollection EngineImpl::counters_;
CounterMap* EngineImpl::counter_map_;
#endif

template <>
struct implement<Engine> {
  using type = EngineImpl;
};

WASM_EXPORT void Engine::destroy() { delete impl(this); }

WASM_EXPORT auto Engine::make(own<Config>&& config) -> own<Engine> {
  auto engine = new (std::nothrow) EngineImpl;
  if (!engine) return own<Engine>();
  engine->platform = i::v8_flags.single_threaded
                         ? v8::platform::NewSingleThreadedDefaultPlatform()
                         : v8::platform::NewDefaultPlatform(
                               i::v8_flags.wasm_capi_thread_pool_size);
  v8::V8::InitializePlatform(engine->platform.get());
  v8::V8::Initialize();

  if (i::v8_flags.prof) {
    i::PrintF(
        "--prof is currently unreliable for V8's Wasm-C-API due to "
        "fast-c-calls.\n");
  }

  return make_own(seal<Engine>(engine));
}

// This should be called somewhat regularly, especially on potentially hot
// sections of pure C++ execution. To achieve that, we call it on API entry
// points that heap-allocate but don't call into generated code.
// For example, finalization of incremental marking is relying on it.
void CheckAndHandleInterrupts(i::Isolate* isolate) {
  i::StackLimitCheck check(isolate);
  if (check.InterruptRequested()) {
    isolate->stack_guard()->HandleInterrupts();
  }
}

// Stores

void StoreImpl::destroy() { delete this; }

StoreImpl::~StoreImpl() {
  {
    v8::Isolate::Scope isolate_scope(isolate_);
#ifdef DEBUG
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
    i_isolate->heap()->PreciseCollectAllGarbage(
        i::GCFlag::kForced, i::GarbageCollectionReason::kTesting,
        v8::kNoGCCallbackFlags);
#endif
    context()->Exit();
  }
  isolate_->Dispose();
  delete create_params_.array_buffer_allocator;
}

struct ManagedData {
  static constexpr i::ExternalPointerTag kManagedTag = i::kWasmManagedDataTag;

  ManagedData(void* info, void (*finalizer)(void*))
      : info(info), finalizer(finalizer) {}

  ~ManagedData() {
    if (finalizer) (*finalizer)(info);
  }

  void* info;
  void (*finalizer)(void*);
};

void StoreImpl::SetHostInfo(i::DirectHandle<i::Object> object, void* info,
                            void (*finalizer)(void*)) {
  v8::Isolate::Scope isolate_scope(isolate());
  i::HandleScope scope(i_isolate());
  // Ideally we would specify the total size kept alive by {info} here,
  // but all we get from the embedder is a {void*}, so our best estimate
  // is the size of the metadata.
  size_t estimated_size = sizeof(ManagedData);
  i::DirectHandle<i::Object> wrapper = i::Managed<ManagedData>::From(
      i_isolate(), estimated_size,
      std::make_shared<ManagedData>(info, finalizer));
  int32_t hash = i::Object::GetOrCreateHash(*object, i_isolate()).value();
  i::JSWeakCollection::Set(host_info_map_, object, wrapper, hash);
}

void* StoreImpl::GetHostInfo(i::DirectHandle<i::Object> key) {
  PtrComprCageAccessScope ptr_compr_cage_access_scope(i_isolate());
  i::Tagged<i::Object> raw =
      i::Cast<i::EphemeronHashTable>(host_info_map_->table())->Lookup(key);
  if (IsTheHole(raw, i_isolate())) return nullptr;
  return i::Cast<i::Managed<ManagedData>>(raw)->raw()->info;
}

template <>
struct implement<Store> {
  using type = StoreImpl;
};

WASM_EXPORT void Store::destroy() { delete impl(this); }

WASM_EXPORT auto Store::make(Engine*) -> own<Store> {
  auto store = make_own(new (std::nothrow) StoreImpl());
  if (!store) return own<Store>();

  // Create isolate.
  store->create_params_.array_buffer_allocator =
      v8::ArrayBuffer::Allocator::NewDefaultAllocator();
#ifdef ENABLE_VTUNE_JIT_INTERFACE
  store->create_params_.code_event_handler = vTune::GetVtuneCodeEventHandler();
#endif
#if DUMP_COUNTERS
  store->create_params_.counter_lookup_callback = EngineImpl::LookupCounter;
  store->create_params_.create_histogram_callback = EngineImpl::CreateHistogram;
  store->create_params_.add_histogram_sample_callback =
      EngineImpl::AddHistogramSample;
#endif
  v8::Isolate* isolate = v8::Isolate::New(store->create_params_);
  if (!isolate) return own<Store>();
  store->isolate_ = isolate;
  isolate->SetData(0, store.get());
  // We intentionally do not call isolate->Enter() here, because that would
  // prevent embedders from using stores with overlapping but non-nested
  // lifetimes. The consequence is that Isolate::Current() is dysfunctional
  // and hence must not be called by anything reachable via this file.

  {
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HandleScope handle_scope(isolate);

    // Create context.
    v8::Local<v8::Context> context = v8::Context::New(isolate);
    if (context.IsEmpty()) return own<Store>();
    context->Enter();  // The Exit() call is in ~StoreImpl.
    store->context_ = v8::Eternal<v8::Context>(isolate, context);

    // Create weak map for Refs with host info.
    i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
    store->host_info_map_ = i_isolate->global_handles()->Create(
        *i_isolate->factory()->NewJSWeakMap());
  }
  // We want stack traces for traps.
  constexpr int kStackLimit = 10;
  isolate->SetCaptureStackTraceForUncaughtExceptions(true, kStackLimit,
                                                     v8::StackTrace::kOverview);

  return make_own(seal<Store>(store.release()));
}

///////////////////////////////////////////////////////////////////////////////
// Type Representations

// Value Types

struct ValTypeImpl {
  ValKind kind;

  explicit ValTypeImpl(ValKind kind) : kind(kind) {}
};

template <>
struct implement<ValType> {
  using type = ValTypeImpl;
};

ValTypeImpl* valtype_i32 = new ValTypeImpl(ValKind::I32);
ValTypeImpl* valtype_i64 = new ValTypeImpl(ValKind::I64);
ValTypeImpl* valtype_f32 = new ValTypeImpl(ValKind::F32);
ValTypeImpl* valtype_f64 = new ValTypeImpl(ValKind::F64);
ValTypeImpl* valtype_externref = new ValTypeImpl(ValKind::EXTERNREF);
ValTypeImpl* valtype_funcref = new ValTypeImpl(ValKind::FUNCREF);

WASM_EXPORT void ValType::destroy() { this->~ValType(); }

WASM_EXPORT own<ValType> ValType::make(ValKind k) {
  ValTypeImpl* valtype;
  switch (k) {
    case ValKind::I32:
      valtype = valtype_i32;
      break;
    case ValKind::I64:
      valtype = valtype_i64;
      break;
    case ValKind::F32:
      valtype = valtype_f32;
      break;
    case ValKind::F64:
      valtype = valtype_f64;
      break;
    case ValKind::EXTERNREF:
      valtype = valtype_externref;
      break;
    case ValKind::FUNCREF:
      valtype = valtype_funcref;
      break;
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
  return own<ValType>(seal<ValType>(valtype));
}

WASM_EXPORT auto ValType::copy() const -> own<ValType> { return make(kind()); }

WASM_EXPORT auto ValType::kind() const -> ValKind { return impl(this)->kind; }

// Extern Types

struct ExternTypeImpl {
  ExternKind kind;

  explicit ExternTypeImpl(ExternKind kind) : kind(kind) {}
  virtual ~ExternTypeImpl() = default;
};

template <>
struct implement<ExternType> {
  using type = ExternTypeImpl;
};

WASM_EXPORT void ExternType::destroy() { delete impl(this); }

WASM_EXPORT auto ExternType::copy() const -> own<ExternType> {
  switch (kind()) {
    case ExternKind::FUNC:
      return func()->copy();
    case ExternKind::GLOBAL:
      return global()->copy();
    case ExternKind::TABLE:
      return table()->copy();
    case ExternKind::MEMORY:
      return memory()->copy();
  }
}

WASM_EXPORT auto ExternType::kind() const -> ExternKind {
  return impl(this)->kind;
}

// Function Types

struct FuncTypeImpl : ExternTypeImpl {
  ownvec<ValType> params;
  ownvec<ValType> results;

  FuncTypeImpl(ownvec<ValType>& params, ownvec<ValType>& results)
      : ExternTypeImpl(ExternKind::FUNC),
        params(std::move(params)),
        results(std::move(results)) {}
};

template <>
struct implement<FuncType> {
  using type = FuncTypeImpl;
};

WASM_EXPORT void FuncType::destroy() { delete impl(this); }

WASM_EXPORT auto FuncType::make(ownvec<ValType>&& params,
                                ownvec<ValType>&& results) -> own<FuncType> {
  return params && results
             ? own<FuncType>(seal<FuncType>(new (std::nothrow)
                                                FuncTypeImpl(params, results)))
             : own<FuncType>();
}

WASM_EXPORT auto FuncType::copy() const -> own<FuncType> {
  return make(params().deep_copy(), results().deep_copy());
}

WASM_EXPORT auto FuncType::params() const -> const ownvec<ValType>& {
  return impl(this)->params;
}

WASM_EXPORT auto FuncType::results() const -> const ownvec<ValType>& {
  return impl(this)->results;
}

WASM_EXPORT auto ExternType::func() -> FuncType* {
  return kind() == ExternKind::FUNC
             ? seal<FuncType>(static_cast<FuncTypeImpl*>(impl(this)))
             : nullptr;
}

WASM_EXPORT auto ExternType::func() const -> const FuncType* {
  return kind() == ExternKind::FUNC
             ? seal<FuncType>(static_cast<const FuncTypeImpl*>(impl(this)))
             : nullptr;
}

// Global Types

struct GlobalTypeImpl : ExternTypeImpl {
  own<ValType> content;
  Mutability mutability;

  GlobalTypeImpl(own<ValType>& content, Mutability mutability)
      : ExternTypeImpl(ExternKind::GLOBAL),
        content(std::move(content)),
        mutability(mutability) {}

  ~GlobalTypeImpl() override = default;
};

template <>
struct implement<GlobalType> {
  using type = GlobalTypeImpl;
};

void GlobalType::destroy() { delete impl(this); }

auto GlobalType::make(own<ValType>&& content, Mutability mutability)
    -> own<GlobalType> {
  return content ? own<GlobalType>(seal<GlobalType>(
                       new (std::nothrow) GlobalTypeImpl(content, mutability)))
                 : own<GlobalType>();
}

auto GlobalType::copy() const -> own<GlobalType> {
  return make(content()->copy(), mutability());
}

auto GlobalType::content() const -> const ValType* {
  return impl(this)->content.get();
}

auto GlobalType::mutability() const -> Mutability {
  return impl(this)->mutability;
}

auto ExternType::global() -> GlobalType* {
  return kind() == ExternKind::GLOBAL
             ? seal<GlobalType>(static_cast<GlobalTypeImpl*>(impl(this)))
             : nullptr;
}

auto ExternType::global() const -> const GlobalType* {
  return kind() == ExternKind::GLOBAL
             ? seal<GlobalType>(static_cast<const GlobalTypeImpl*>(impl(this)))
             : nullptr;
}

// Table Types

struct TableTypeImpl : ExternTypeImpl {
  own<ValType> element;
  Limits limits;

  TableTypeImpl(own<ValType>& element, Limits limits)
      : ExternTypeImpl(ExternKind::TABLE),
        element(std::move(element)),
        limits(limits) {}

  ~TableTypeImpl() override = default;
};

template <>
struct implement<TableType> {
  using type = TableTypeImpl;
};

WASM_EXPORT void TableType::destroy() { delete impl(this); }

WASM_EXPORT auto TableType::make(own<ValType>&& element, Limits limits)
    -> own<TableType> {
  return element ? own<TableType>(seal<TableType>(
                       new (std::nothrow) TableTypeImpl(element, limits)))
                 : own<TableType>();
}

WASM_EXPORT auto TableType::copy() const -> own<TableType> {
  return make(element()->copy(), limits());
}

WASM_EXPORT auto TableType::element() const -> const ValType* {
  return impl(this)->element.get();
}

WASM_EXPORT auto TableType::limits() const -> const Limits& {
  return impl(this)->limits;
}

WASM_EXPORT auto ExternType::table() -> TableType* {
  return kind() == ExternKind::TABLE
             ? seal<TableType>(static_cast<TableTypeImpl*>(impl(this)))
             : nullptr;
}

WASM_EXPORT auto ExternType::table() const -> const TableType* {
  return kind() == ExternKind::TABLE
             ? seal<TableType>(static_cast<const TableTypeImpl*>(impl(this)))
             : nullptr;
}

// Memory Types

struct MemoryTypeImpl : ExternTypeImpl {
  Limits limits;

  explicit MemoryTypeImpl(Limits limits)
      : ExternTypeImpl(ExternKind::MEMORY), limits(limits) {}

  ~MemoryTypeImpl() override = default;
};

template <>
struct implement<MemoryType> {
  using type = MemoryTypeImpl;
};

WASM_EXPORT auto MemoryType::make(Limits limits) -> own<MemoryType> {
  return own<MemoryType>(
      seal<MemoryType>(new (std::nothrow) MemoryTypeImpl(limits)));
}

void MemoryType::destroy() { delete impl(this); }

WASM_EXPORT auto MemoryType::copy() const -> own<MemoryType> {
  return MemoryType::make(limits());
}

WASM_EXPORT auto MemoryType::limits() const -> const Limits& {
  return impl(this)->limits;
}

WASM_EXPORT auto ExternType::memory() -> MemoryType* {
  return kind() == ExternKind::MEMORY
             ? seal<MemoryType>(static_cast<MemoryTypeImpl*>(impl(this)))
             : nullptr;
}

WASM_EXPORT auto ExternType::memory() const -> const MemoryType* {
  return kind() == ExternKind::MEMORY
             ? seal<MemoryType>(static_cast<const MemoryTypeImpl*>(impl(this)))
             : nullptr;
}

// Import Types

struct ImportTypeImpl {
  Name module;
  Name name;
  own<ExternType> type;

  ImportTypeImpl(Name& module, Name& name, own<ExternType>& type)
      : module(std::move(module)),
        name(std::move(name)),
        type(std::move(type)) {}
};

template <>
struct implement<ImportType> {
  using type = ImportTypeImpl;
};

WASM_EXPORT void ImportType::destroy() { delete impl(this); }

WASM_EXPORT auto ImportType::make(Name&& module, Name&& name,
                                  own<ExternType>&& type) -> own<ImportType> {
  return module && name && type
             ? own<ImportType>(seal<ImportType>(
                   new (std::nothrow) ImportTypeImpl(module, name, type)))
             : own<ImportType>();
}

WASM_EXPORT auto ImportType::copy() const -> own<ImportType> {
  return make(module().copy(), name().copy(), type()->copy());
}

WASM_EXPORT auto ImportType::module() const -> const Name& {
  return impl(this)->module;
}

WASM_EXPORT auto ImportType::name() const -> const Name& {
  return impl(this)->name;
}

WASM_EXPORT auto ImportType::type() const -> const ExternType* {
  return impl(this)->type.get();
}

// Export Types

struct ExportTypeImpl {
  Name name;
  own<ExternType> type;

  ExportTypeImpl(Name& name, own<ExternType>& type)
      : name(std::move(name)), type(std::move(type)) {}
};

template <>
struct implement<ExportType> {
  using type = ExportTypeImpl;
};

WASM_EXPORT void ExportType::destroy() { delete impl(this); }

WASM_EXPORT auto ExportType::make(Name&& name, own<ExternType>&& type)
    -> own<ExportType> {
  return name && type ? own<ExportType>(seal<ExportType>(
                            new (std::nothrow) ExportTypeImpl(name, type)))
                      : own<ExportType>();
}

WASM_EXPORT auto ExportType::copy() const -> own<ExportType> {
  return make(name().copy(), type()->copy());
}

WASM_EXPORT auto ExportType::name() const -> const Name& {
  return impl(this)->name;
}

WASM_EXPORT auto ExportType::type() const -> const ExternType* {
  return impl(this)->type.get();
}

i::DirectHandle<i::String> VecToString(i::Isolate* isolate,
                                       const vec<byte_t>& chars) {
  size_t length = chars.size();
  // Some, but not all, {chars} vectors we get here are null-terminated,
  // so let's be robust to that.
  if (length > 0 && chars[length - 1] == 0) length--;
  return isolate->factory()
      ->NewStringFromUtf8(std::string_view{chars.get(), length})
      .ToHandleChecked();
}

// References

template <class Ref, class JSType>
class RefImpl {
 public:
  static own<Ref> make(StoreImpl* store, i::DirectHandle<JSType> obj) {
    RefImpl* self = new (std::nothrow) RefImpl();
    if (!self) return nullptr;
    self->store_ = store;
    v8::Isolate::Scope isolate_scope(store->isolate());
    self->val_ = store->i_isolate()->global_handles()->Create(*obj);
    return make_own(seal<Ref>(self));
  }

  ~RefImpl() { i::GlobalHandles::Destroy(location()); }

  own<Ref> copy() const {
    v8::Isolate::Scope isolate_scope(store()->isolate());
    return make(store(), v8_object());
  }

  StoreImpl* store() const { return store_; }

  i::Isolate* isolate() const { return store()->i_isolate(); }

  i::DirectHandle<JSType> v8_object() const { return i::Cast<JSType>(val_); }

  void* get_host_info() const {
    v8::Isolate::Scope isolate_scope(store()->isolate());
    return store()->GetHostInfo(v8_object());
  }

  void set_host_info(void* info, void (*finalizer)(void*)) {
    v8::Isolate::Scope isolate_scope(store()->isolate());
    store()->SetHostInfo(v8_object(), info, finalizer);
  }

 private:
  RefImpl() = default;

  i::Address* location() const {
    return reinterpret_cast<i::Address*>(val_.address());
  }

  i::IndirectHandle<i::JSReceiver> val_;
  StoreImpl* store_;
};

template <>
struct implement<Ref> {
  using type = RefImpl<Ref, i::JSReceiver>;
};

WASM_EXPORT void Ref::destroy() { delete impl(this); }

WASM_EXPORT auto Ref::copy() const -> own<Ref> { return impl(this)->copy(); }

WASM_EXPORT auto Ref::same(const Ref* that) const -> bool {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::HandleScope handle_scope(isolate);
  return i::Object::SameValue(*impl(this)->v8_object(),
                              *impl(that)->v8_object());
}

WASM_EXPORT auto Ref::get_host_info() const -> void* {
  return impl(this)->get_host_info();
}

WASM_EXPORT void Ref::set_host_info(void* info, void (*finalizer)(void*)) {
  impl(this)->set_host_info(info, finalizer);
}

///////////////////////////////////////////////////////////////////////////////
// Runtime Objects

// Frames

namespace {

struct FrameImpl {
  FrameImpl(own<Instance>&& instance, uint32_t func_index, size_t func_offset,
            size_t module_offset)
      : instance(std::move(instance)),
        func_index(func_index),
        func_offset(func_offset),
        module_offset(module_offset) {}

  own<Instance> instance;
  uint32_t func_index;
  size_t func_offset;
  size_t module_offset;
};

}  // namespace

template <>
struct implement<Frame> {
  using type = FrameImpl;
};

WASM_EXPORT void Frame::destroy() { delete impl(this); }

WASM_EXPORT own<Frame> Frame::copy() const {
  auto self = impl(this);
  return own<Frame>(seal<Frame>(
      new (std::nothrow) FrameImpl(self->instance->copy(), self->func_index,
                                   self->func_offset, self->module_offset)));
}

WASM_EXPORT Instance* Frame::instance() const {
  return impl(this)->instance.get();
}

WASM_EXPORT uint32_t Frame::func_index() const {
  return impl(this)->func_index;
}

WASM_EXPORT size_t Frame::func_offset() const {
  return impl(this)->func_offset;
}

WASM_EXPORT size_t Frame::module_offset() const {
  return impl(this)->module_offset;
}

// Traps

template <>
struct implement<Trap> {
  using type = RefImpl<Trap, i::JSReceiver>;
};

WASM_EXPORT void Trap::destroy() { delete impl(this); }

WASM_EXPORT auto Trap::copy() const -> own<Trap> { return impl(this)->copy(); }

WASM_EXPORT auto Trap::make(Store* store_abs, const Message& message)
    -> own<Trap> {
  auto store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  i::DirectHandle<i::String> string = VecToString(isolate, message);
  i::DirectHandle<i::JSObject> exception =
      isolate->factory()->NewError(isolate->error_function(), string);
  i::JSObject::AddProperty(isolate, exception,
                           isolate->factory()->wasm_uncatchable_symbol(),
                           isolate->factory()->true_value(), i::NONE);
  return implement<Trap>::type::make(store, exception);
}

WASM_EXPORT auto Trap::message() const -> Message {
  auto isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::HandleScope handle_scope(isolate);

  i::DirectHandle<i::JSMessageObject> message =
      isolate->CreateMessage(impl(this)->v8_object(), nullptr);
  i::DirectHandle<i::String> result =
      i::MessageHandler::GetMessage(isolate, message);
  result = i::String::Flatten(isolate, result);  // For performance.
  size_t length = 0;
  std::unique_ptr<char[]> utf8 = result->ToCString(&length);
  return vec<byte_t>::adopt(length, utf8.release());
}

namespace {

own<Instance> GetInstance(StoreImpl* store,
                          i::DirectHandle<i::WasmInstanceObject> instance);

own<Frame> CreateFrameFromInternal(i::DirectHandle<i::FixedArray> frames,
                                   int index, i::Isolate* isolate,
                                   StoreImpl* store) {
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::DirectHandle<i::CallSiteInfo> frame(
      i::Cast<i::CallSiteInfo>(frames->get(index)), isolate);
  i::DirectHandle<i::WasmInstanceObject> instance(frame->GetWasmInstance(),
                                                  isolate);
  uint32_t func_index = frame->GetWasmFunctionIndex();
  size_t module_offset = i::CallSiteInfo::GetSourcePosition(frame);
  size_t func_offset = module_offset - i::wasm::GetWasmFunctionOffset(
                                           instance->module(), func_index);
  return own<Frame>(seal<Frame>(new (std::nothrow) FrameImpl(
      GetInstance(store, instance), func_index, func_offset, module_offset)));
}

}  // namespace

own<Frame> Trap::origin() const {
  i::Isolate* isolate = impl(this)->isolate();
  PtrComprCageAccessScope ptr_compr_cage_access_scope(impl(this)->isolate());
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::HandleScope handle_scope(isolate);

  i::DirectHandle<i::FixedArray> frames =
      isolate->GetSimpleStackTrace(impl(this)->v8_object());
  if (frames->length() == 0) {
    return own<Frame>();
  }
  return CreateFrameFromInternal(frames, 0, isolate, impl(this)->store());
}

ownvec<Frame> Trap::trace() const {
  i::Isolate* isolate = impl(this)->isolate();
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::HandleScope handle_scope(isolate);

  i::DirectHandle<i::FixedArray> frames =
      isolate->GetSimpleStackTrace(impl(this)->v8_object());
  int num_frames = frames->length();
  // {num_frames} can be 0; the code below can handle that case.
  ownvec<Frame> result = ownvec<Frame>::make_uninitialized(num_frames);
  for (int i = 0; i < num_frames; i++) {
    result[i] =
        CreateFrameFromInternal(frames, i, isolate, impl(this)->store());
  }
  return result;
}

// Foreign Objects

template <>
struct implement<Foreign> {
  using type = RefImpl<Foreign, i::JSReceiver>;
};

WASM_EXPORT void Foreign::destroy() { delete impl(this); }

WASM_EXPORT auto Foreign::copy() const -> own<Foreign> {
  return impl(this)->copy();
}

WASM_EXPORT auto Foreign::make(Store* store_abs) -> own<Foreign> {
  StoreImpl* store = impl(store_abs);
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::Isolate* isolate = store->i_isolate();
  i::HandleScope handle_scope(isolate);

  i::DirectHandle<i::JSObject> obj =
      isolate->factory()->NewJSObject(isolate->object_function());
  return implement<Foreign>::type::make(store, obj);
}

// Modules

template <>
struct implement<Module> {
  using type = RefImpl<Module, i::WasmModuleObject>;
};

WASM_EXPORT void Module::destroy() { delete impl(this); }

WASM_EXPORT auto Module::copy() const -> own<Module> {
  return impl(this)->copy();
}

WASM_EXPORT auto Module::validate(Store* store_abs, const vec<byte_t>& binary)
    -> bool {
  i::Isolate* isolate = impl(store_abs)->i_isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::HandleScope scope(isolate);
  v8::base::Vector<const uint8_t> bytes = v8::base::VectorOf(
      reinterpret_cast<const uint8_t*>(binary.get()), binary.size());
  i::wasm::WasmEnabledFeatures features =
      i::wasm::WasmEnabledFeatures::FromIsolate(isolate);
  i::wasm::CompileTimeImports imports;
  return i::wasm::GetWasmEngine()->SyncValidate(isolate, features,
                                                std::move(imports), bytes);
}

WASM_EXPORT auto Module::make(Store* store_abs, const vec<byte_t>& binary)
    -> own<Module> {
  StoreImpl* store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope scope(isolate);
  CheckAndHandleInterrupts(isolate);
  v8::base::OwnedVector<const uint8_t> bytes = v8::base::OwnedCopyOf(
      reinterpret_cast<const uint8_t*>(binary.get()), binary.size());
  i::wasm::WasmEnabledFeatures features =
      i::wasm::WasmEnabledFeatures::FromIsolate(isolate);
  i::wasm::CompileTimeImports imports;
  i::wasm::ErrorThrower thrower(isolate, "ignored");
  i::DirectHandle<i::WasmModuleObject> module;
  if (!i::wasm::GetWasmEngine()
           ->SyncCompile(isolate, features, std::move(imports), &thrower,
                         std::move(bytes))
           .ToHandle(&module)) {
    thrower.Reset();  // The API provides no way to expose the error.
    return nullptr;
  }
  return implement<Module>::type::make(store, module);
}

WASM_EXPORT auto Module::imports() const -> ownvec<ImportType> {
  const i::wasm::NativeModule* native_module =
      impl(this)->v8_object()->native_module();
  const i::wasm::WasmModule* module = native_module->module();
  const v8::base::Vector<const uint8_t> wire_bytes =
      native_module->wire_bytes();
  const std::vector<i::wasm::WasmImport>& import_table = module->import_table;
  size_t size = import_table.size();
  ownvec<ImportType> imports = ownvec<ImportType>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; i++) {
    const i::wasm::WasmImport& imp = import_table[i];
    Name module_name = GetNameFromWireBytes(imp.module_name, wire_bytes);
    Name name = GetNameFromWireBytes(imp.field_name, wire_bytes);
    own<ExternType> type = GetImportExportType(module, imp.kind, imp.index);
    imports[i] = ImportType::make(std::move(module_name), std::move(name),
                                  std::move(type));
  }
  return imports;
}

ownvec<ExportType> ExportsImpl(
    i::DirectHandle<i::WasmModuleObject> module_obj) {
  const i::wasm::NativeModule* native_module = module_obj->native_module();
  const i::wasm::WasmModule* module = native_module->module();
  const v8::base::Vector<const uint8_t> wire_bytes =
      native_module->wire_bytes();
  const std::vector<i::wasm::WasmExport>& export_table = module->export_table;
  size_t size = export_table.size();
  ownvec<ExportType> exports = ownvec<ExportType>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; i++) {
    const i::wasm::WasmExport& exp = export_table[i];
    Name name = GetNameFromWireBytes(exp.name, wire_bytes);
    own<ExternType> type = GetImportExportType(module, exp.kind, exp.index);
    exports[i] = ExportType::make(std::move(name), std::move(type));
  }
  return exports;
}

WASM_EXPORT auto Module::exports() const -> ownvec<ExportType> {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  return ExportsImpl(impl(this)->v8_object());
}

// We tier up all functions to TurboFan, and then serialize all TurboFan code.
// If no TurboFan code existed before calling this function, then the call to
// {serialize} may take a long time.
WASM_EXPORT auto Module::serialize() const -> vec<byte_t> {
  i::Isolate* isolate = impl(this)->isolate();
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::wasm::NativeModule* native_module =
      impl(this)->v8_object()->native_module();
  native_module->compilation_state()->TierUpAllFunctions();
  v8::base::Vector<const uint8_t> wire_bytes = native_module->wire_bytes();
  size_t binary_size = wire_bytes.size();
  i::wasm::WasmSerializer serializer(native_module);
  size_t serial_size = serializer.GetSerializedNativeModuleSize();
  size_t size_size = i::wasm::LEBHelper::sizeof_u64v(binary_size);
  vec<byte_t> buffer =
      vec<byte_t>::make_uninitialized(size_size + binary_size + serial_size);
  byte_t* ptr = buffer.get();
  i::wasm::LEBHelper::write_u64v(reinterpret_cast<uint8_t**>(&ptr),
                                 binary_size);
  std::memcpy(ptr, wire_bytes.begin(), binary_size);
  ptr += binary_size;
  if (!serializer.SerializeNativeModule(
          {reinterpret_cast<uint8_t*>(ptr), serial_size})) {
    // Serialization fails if no TurboFan code is present. This may happen
    // because the module does not have any functions, or because another thread
    // modifies the {NativeModule} concurrently. In this case, the serialized
    // module just contains the wire bytes.
    buffer = vec<byte_t>::make_uninitialized(size_size + binary_size);
    byte_t* pointer = buffer.get();
    i::wasm::LEBHelper::write_u64v(reinterpret_cast<uint8_t**>(&pointer),
                                   binary_size);
    std::memcpy(pointer, wire_bytes.begin(), binary_size);
  }
  return buffer;
}

WASM_EXPORT auto Module::deserialize(Store* store_abs,
                                     const vec<byte_t>& serialized)
    -> own<Module> {
  StoreImpl* store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  const byte_t* ptr = serialized.get();
  size_t binary_size = static_cast<size_t>(ReadLebU64(&ptr));
  ptrdiff_t size_size = ptr - serialized.get();
  size_t serial_size = serialized.size() - size_size - binary_size;
  v8::base::OwnedVector<const uint8_t> wire_bytes =
      v8::base::OwnedCopyOf(reinterpret_cast<const uint8_t*>(ptr), binary_size);
  i::DirectHandle<i::WasmModuleObject> module_obj;
  if (serial_size > 0) {
    // The C-API does not allow passing compile imports.
    // We thus use an empty `CompileTimeImports` object, analogous to module
    // creation.
    // Note that in contrast to the JS API, this does not handle different
    // denormals flushing modes (see base::FPU::GetFlushDenormals /
    // `CompileTimeImport::kDisableDenormalFloats`).
    i::wasm::CompileTimeImports compile_imports;
    i::wasm::WasmEnabledFeatures features =
        i::wasm::WasmEnabledFeatures::FromIsolate(isolate);
    if (!i::wasm::DeserializeNativeModule(
             isolate, features,
             {reinterpret_cast<const uint8_t*>(ptr + binary_size), serial_size},
             wire_bytes, compile_imports, {})
             .ToHandle(&module_obj)) {
      // We were given a serialized module, but failed to deserialize. Report
      // this as an error.
      return nullptr;
    }
  } else {
    // No serialized module was given. This is fine, just create a module from
    // scratch.
    vec<byte_t> binary = vec<byte_t>::make_uninitialized(binary_size);
    std::memcpy(binary.get(), ptr, binary_size);
    return make(store_abs, binary);
  }
  return implement<Module>::type::make(store, module_obj);
}

// TODO(v8): do better when V8 can do better.
template <>
struct implement<Shared<Module>> {
  using type = vec<byte_t>;
};

WASM_EXPORT void Shared<Module>::destroy() { delete impl(this); }

WASM_EXPORT auto Module::share() const -> own<Shared<Module>> {
  auto shared = seal<Shared<Module>>(new vec<byte_t>(serialize()));
  return make_own(shared);
}

WASM_EXPORT auto Module::obtain(Store* store, const Shared<Module>* shared)
    -> own<Module> {
  return Module::deserialize(store, *impl(shared));
}

// Externals

template <>
struct implement<Extern> {
  using type = RefImpl<Extern, i::JSReceiver>;
};

WASM_EXPORT void Extern::destroy() { delete impl(this); }

WASM_EXPORT auto Extern::copy() const -> own<Extern> {
  return impl(this)->copy();
}

WASM_EXPORT auto Extern::kind() const -> ExternKind {
  i::Isolate* isolate = impl(this)->isolate();
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));

  i::DirectHandle<i::JSReceiver> obj = impl(this)->v8_object();
  if (i::WasmExternalFunction::IsWasmExternalFunction(*obj)) {
    return wasm::ExternKind::FUNC;
  }
  if (IsWasmGlobalObject(*obj)) return wasm::ExternKind::GLOBAL;
  if (IsWasmTableObject(*obj)) return wasm::ExternKind::TABLE;
  if (IsWasmMemoryObject(*obj)) return wasm::ExternKind::MEMORY;
  UNREACHABLE();
}

WASM_EXPORT auto Extern::type() const -> own<ExternType> {
  switch (kind()) {
    case ExternKind::FUNC:
      return func()->type();
    case ExternKind::GLOBAL:
      return global()->type();
    case ExternKind::TABLE:
      return table()->type();
    case ExternKind::MEMORY:
      return memory()->type();
  }
}

WASM_EXPORT auto Extern::func() -> Func* {
  return kind() == ExternKind::FUNC ? static_cast<Func*>(this) : nullptr;
}

WASM_EXPORT auto Extern::global() -> Global* {
  return kind() == ExternKind::GLOBAL ? static_cast<Global*>(this) : nullptr;
}

WASM_EXPORT auto Extern::table() -> Table* {
  return kind() == ExternKind::TABLE ? static_cast<Table*>(this) : nullptr;
}

WASM_EXPORT auto Extern::memory() -> Memory* {
  return kind() == ExternKind::MEMORY ? static_cast<Memory*>(this) : nullptr;
}

WASM_EXPORT auto Extern::func() const -> const Func* {
  return kind() == ExternKind::FUNC ? static_cast<const Func*>(this) : nullptr;
}

WASM_EXPORT auto Extern::global() const -> const Global* {
  return kind() == ExternKind::GLOBAL ? static_cast<const Global*>(this)
                                      : nullptr;
}

WASM_EXPORT auto Extern::table() const -> const Table* {
  return kind() == ExternKind::TABLE ? static_cast<const Table*>(this)
                                     : nullptr;
}

WASM_EXPORT auto Extern::memory() const -> const Memory* {
  return kind() == ExternKind::MEMORY ? static_cast<const Memory*>(this)
                                      : nullptr;
}

auto extern_to_v8(const Extern* ex) -> i::DirectHandle<i::JSReceiver> {
  return impl(ex)->v8_object();
}

// Function Instances

template <>
struct implement<Func> {
  using type = RefImpl<Func, i::JSFunction>;
};

WASM_EXPORT void Func::destroy() { delete impl(this); }

WASM_EXPORT auto Func::copy() const -> own<Func> { return impl(this)->copy(); }

struct FuncData {
  static constexpr i::ExternalPointerTag kManagedTag = i::kWasmFuncDataTag;

  Store* store;
  own<FuncType> type;
  enum Kind { kCallback, kCallbackWithEnv } kind;
  union {
    Func::callback callback;
    Func::callback_with_env callback_with_env;
  };
  void (*finalizer)(void*);
  void* env;

  FuncData(Store* store, const FuncType* type, Kind kind)
      : store(store),
        type(type->copy()),
        kind(kind),
        finalizer(nullptr),
        env(nullptr) {}

  ~FuncData() {
    if (finalizer) (*finalizer)(env);
  }

  static i::Address v8_callback(i::Address host_data_foreign, i::Address argv);
};

namespace {

class SignatureHelper : public i::AllStatic {
 public:
  static const i::wasm::CanonicalTypeIndex Canonicalize(FuncType* type) {
    std::vector<i::wasm::ValueType> types;
    types.reserve(type->results().size() + type->params().size());

    // TODO(jkummerow): Consider making vec<> range-based for-iterable.
    for (size_t i = 0; i < type->results().size(); i++) {
      types.push_back(WasmValKindToV8(type->results()[i]->kind()));
    }
    for (size_t i = 0; i < type->params().size(); i++) {
      types.push_back(WasmValKindToV8(type->params()[i]->kind()));
    }

    i::wasm::FunctionSig non_canonical_sig{type->results().size(),
                                           type->params().size(), types.data()};
    return i::wasm::GetTypeCanonicalizer()->AddRecursiveGroup(
        &non_canonical_sig);
  }

  static own<FuncType> FromV8Sig(const i::wasm::CanonicalSig* sig) {
    int result_arity = static_cast<int>(sig->return_count());
    int param_arity = static_cast<int>(sig->parameter_count());
    ownvec<ValType> results = ownvec<ValType>::make_uninitialized(result_arity);
    ownvec<ValType> params = ownvec<ValType>::make_uninitialized(param_arity);

    for (int i = 0; i < result_arity; ++i) {
      results[i] = ValType::make(V8ValueTypeToWasm(sig->GetReturn(i)));
    }
    for (int i = 0; i < param_arity; ++i) {
      params[i] = ValType::make(V8ValueTypeToWasm(sig->GetParam(i)));
    }
    return FuncType::make(std::move(params), std::move(results));
  }

  static const i::wasm::CanonicalSig* GetSig(
      i::DirectHandle<i::JSFunction> function) {
    return i::Cast<i::WasmCapiFunction>(*function)->sig();
  }

#if V8_ENABLE_SANDBOX
  // Wraps {FuncType} so it has the same interface as {v8::internal::Signature}.
  struct FuncTypeAdapter {
    const FuncType* type = nullptr;
    size_t parameter_count() const { return type->params().size(); }
    size_t return_count() const { return type->results().size(); }
    i::wasm::ValueType GetParam(size_t i) const {
      return WasmValKindToV8(type->params()[i]->kind());
    }
    i::wasm::ValueType GetReturn(size_t i) const {
      return WasmValKindToV8(type->results()[i]->kind());
    }
  };
  static uint64_t Hash(FuncType* type) {
    FuncTypeAdapter adapter{type};
    return i::wasm::SignatureHasher::Hash(&adapter);
  }
#endif
};

auto make_func(Store* store_abs, std::shared_ptr<FuncData> data) -> own<Func> {
  auto store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  CheckAndHandleInterrupts(isolate);
  i::DirectHandle<i::Managed<FuncData>> embedder_data =
      i::Managed<FuncData>::From(isolate, sizeof(FuncData), data);
  i::wasm::CanonicalTypeIndex sig_index =
      SignatureHelper::Canonicalize(data->type.get());
  const i::wasm::CanonicalSig* sig =
      i::wasm::GetTypeCanonicalizer()->LookupFunctionSignature(sig_index);
  i::DirectHandle<i::WasmCapiFunction> function = i::WasmCapiFunction::New(
      isolate, reinterpret_cast<i::Address>(&FuncData::v8_callback),
      embedder_data, sig);
  i::TrustedCast<i::WasmImportData>(
      function->shared()->wasm_capi_function_data()->internal()->implicit_arg())
      ->set_callable(*function);
  auto func = implement<Func>::type::make(store, function);
  return func;
}

}  // namespace

WASM_EXPORT auto Func::make(Store* store, const FuncType* type,
                            Func::callback callback) -> own<Func> {
  auto data = std::make_shared<FuncData>(store, type, FuncData::kCallback);
  data->callback = callback;
  return make_func(store, data);
}

WASM_EXPORT auto Func::make(Store* store, const FuncType* type,
                            callback_with_env callback, void* env,
                            void (*finalizer)(void*)) -> own<Func> {
  auto data =
      std::make_shared<FuncData>(store, type, FuncData::kCallbackWithEnv);
  data->callback_with_env = callback;
  data->env = env;
  data->finalizer = finalizer;
  return make_func(store, data);
}

WASM_EXPORT auto Func::type() const -> own<FuncType> {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::DirectHandle<i::JSFunction> func = impl(this)->v8_object();
  if (i::WasmCapiFunction::IsWasmCapiFunction(*func)) {
    return SignatureHelper::FromV8Sig(SignatureHelper::GetSig(func));
  }
  DCHECK(i::WasmExportedFunction::IsWasmExportedFunction(*func));
  auto function = i::Cast<i::WasmExportedFunction>(func);
  auto data = function->shared()->wasm_exported_function_data();
  return FunctionSigToFuncType(
      data->instance_data()->module()->functions[data->function_index()].sig);
}

WASM_EXPORT auto Func::param_arity() const -> size_t {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::DirectHandle<i::JSFunction> func = impl(this)->v8_object();
  if (i::WasmCapiFunction::IsWasmCapiFunction(*func)) {
    return SignatureHelper::GetSig(func)->parameter_count();
  }
  DCHECK(i::WasmExportedFunction::IsWasmExportedFunction(*func));
  auto function = i::Cast<i::WasmExportedFunction>(func);
  auto data = function->shared()->wasm_exported_function_data();
  const i::wasm::FunctionSig* sig =
      data->instance_data()->module()->functions[data->function_index()].sig;
  return sig->parameter_count();
}

WASM_EXPORT auto Func::result_arity() const -> size_t {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::DirectHandle<i::JSFunction> func = impl(this)->v8_object();
  if (i::WasmCapiFunction::IsWasmCapiFunction(*func)) {
    return SignatureHelper::GetSig(func)->return_count();
  }
  DCHECK(i::WasmExportedFunction::IsWasmExportedFunction(*func));
  auto function = i::Cast<i::WasmExportedFunction>(func);
  auto data = function->shared()->wasm_exported_function_data();
  const i::wasm::FunctionSig* sig =
      data->instance_data()->module()->functions[data->function_index()].sig;
  return sig->return_count();
}

namespace {

own<Ref> V8RefValueToWasm(StoreImpl* store, i::DirectHandle<i::Object> value) {
  if (IsNull(*value, store->i_isolate())) return nullptr;
  return implement<Ref>::type::make(store, i::Cast<i::JSReceiver>(value));
}

i::DirectHandle<i::Object> WasmRefToV8(i::Isolate* isolate, const Ref* ref) {
  if (ref == nullptr) return isolate->factory()->null_value();
  return impl(ref)->v8_object();
}

void PrepareFunctionData(
    i::Isolate* isolate,
    i::DirectHandle<i::WasmExportedFunctionData> function_data,
    const i::wasm::CanonicalSig* sig) {
  // If the data is already populated, return immediately.
  // TODO(saelo): We need to use full pointer comparison here while not all Code
  // objects have migrated into trusted space.
  static_assert(!i::kAllCodeObjectsLiveInTrustedSpace);
  if (!function_data->c_wrapper_code(isolate).SafeEquals(
          *BUILTIN_CODE(isolate, Illegal))) {
    return;
  }
  // Compile wrapper code.
  i::DirectHandle<i::Code> wrapper_code =
      i::compiler::CompileCWasmEntry(isolate, sig);
  function_data->set_c_wrapper_code(*wrapper_code);
  // Compute packed args size.
  function_data->set_packed_args_size(
      i::wasm::CWasmArgumentsPacker::TotalSize(sig));
}

void PushArgs(const i::wasm::CanonicalSig* sig, const vec<Val>& args,
              i::wasm::CWasmArgumentsPacker* packer, StoreImpl* store) {
  for (size_t i = 0; i < sig->parameter_count(); i++) {
    i::wasm::CanonicalValueType type = sig->GetParam(i);
    switch (type.kind()) {
      case i::wasm::kI32:
        packer->Push(args[i].i32());
        break;
      case i::wasm::kI64:
        packer->Push(args[i].i64());
        break;
      case i::wasm::kF32:
        packer->Push(args[i].f32());
        break;
      case i::wasm::kF64:
        packer->Push(args[i].f64());
        break;
      case i::wasm::kRef:
      case i::wasm::kRefNull:
        // TODO(14034): Make sure this works for all heap types.
        packer->Push((*WasmRefToV8(store->i_isolate(), args[i].ref())).ptr());
        break;
      case i::wasm::kS128:
        // TODO(14034): Implement.
        UNIMPLEMENTED();
      case i::wasm::kI8:
      case i::wasm::kI16:
      case i::wasm::kF16:
      case i::wasm::kVoid:
      case i::wasm::kTop:
      case i::wasm::kBottom:
        UNREACHABLE();
    }
  }
}

void PopArgs(const i::wasm::CanonicalSig* sig, vec<Val>& results,
             i::wasm::CWasmArgumentsPacker* packer, StoreImpl* store) {
  packer->Reset();
  for (size_t i = 0; i < sig->return_count(); i++) {
    i::wasm::CanonicalValueType type = sig->GetReturn(i);
    switch (type.kind()) {
      case i::wasm::kI32:
        results[i] = Val(packer->Pop<int32_t>());
        break;
      case i::wasm::kI64:
        results[i] = Val(packer->Pop<int64_t>());
        break;
      case i::wasm::kF32:
        results[i] = Val(packer->Pop<float>());
        break;
      case i::wasm::kF64:
        results[i] = Val(packer->Pop<double>());
        break;
      case i::wasm::kRef:
      case i::wasm::kRefNull: {
        // TODO(14034): Make sure this works for all heap types.
        i::Address raw = packer->Pop<i::Address>();
        i::DirectHandle<i::Object> obj(i::Tagged<i::Object>(raw),
                                       store->i_isolate());
        results[i] = Val(V8RefValueToWasm(store, obj));
        break;
      }
      case i::wasm::kS128:
        // TODO(14034): Implement.
        UNIMPLEMENTED();
      case i::wasm::kI8:
      case i::wasm::kI16:
      case i::wasm::kF16:
      case i::wasm::kVoid:
      case i::wasm::kTop:
      case i::wasm::kBottom:
        UNREACHABLE();
    }
  }
}

own<Trap> CallWasmCapiFunction(i::Tagged<i::WasmCapiFunctionData> data,
                               const vec<Val>& args, vec<Val>& results) {
  FuncData* func_data =
      i::Cast<i::Managed<FuncData>>(data->embedder_data())->raw();
  if (func_data->kind == FuncData::kCallback) {
    return (func_data->callback)(args, results);
  }
  DCHECK(func_data->kind == FuncData::kCallbackWithEnv);
  return (func_data->callback_with_env)(func_data->env, args, results);
}

i::DirectHandle<i::JSReceiver> GetProperException(
    i::Isolate* isolate, i::DirectHandle<i::Object> maybe_exception) {
  if (IsJSReceiver(*maybe_exception)) {
    return i::Cast<i::JSReceiver>(maybe_exception);
  }
  if (v8::internal::IsTerminationException(*maybe_exception)) {
    i::DirectHandle<i::String> string =
        isolate->factory()->NewStringFromAsciiChecked("TerminationException");
    return isolate->factory()->NewError(isolate->error_function(), string);
  }
  i::MaybeDirectHandle<i::String> maybe_string =
      i::Object::ToString(isolate, maybe_exception);
  i::DirectHandle<i::String> string = isolate->factory()->empty_string();
  if (!maybe_string.ToHandle(&string)) {
    // If converting the {maybe_exception} to string threw another exception,
    // just give up and leave {string} as the empty string.
    isolate->clear_exception();
  }
  // {NewError} cannot fail when its input is a plain String, so we always
  // get an Error object here.
  return i::Cast<i::JSReceiver>(
      isolate->factory()->NewError(isolate->error_function(), string));
}

}  // namespace

WASM_EXPORT auto Func::call(const vec<Val>& args, vec<Val>& results) const
    -> own<Trap> {
  auto func = impl(this);
  auto store = func->store();
  auto isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  i::Tagged<i::Object> raw_function_data =
      func->v8_object()->shared()->GetTrustedData(isolate);

  // WasmCapiFunctions can be called directly.
  if (i::Tagged<i::WasmCapiFunctionData> data;
      TryCast(raw_function_data, &data)) {
    return CallWasmCapiFunction(data, args, results);
  }

  i::DirectHandle<i::WasmExportedFunctionData> function_data{
      i::SbxCast<i::WasmExportedFunctionData>(raw_function_data), isolate};
  i::DirectHandle<i::WasmTrustedInstanceData> instance_data{
      function_data->instance_data(), isolate};
  int function_index = function_data->function_index();
  const i::wasm::WasmModule* module = instance_data->module();
  // Caching {sig} would reduce overhead substantially.
  const i::wasm::CanonicalSig* sig =
      i::wasm::GetTypeCanonicalizer()->LookupFunctionSignature(
          module->canonical_sig_id(
              module->functions[function_index].sig_index));
  PrepareFunctionData(isolate, function_data, sig);
  i::DirectHandle<i::Code> wrapper_code(function_data->c_wrapper_code(isolate),
                                        isolate);
  i::WasmCodePointer call_target = function_data->internal()->call_target();

  i::wasm::CWasmArgumentsPacker packer(function_data->packed_args_size());
  PushArgs(sig, args, &packer, store);

  i::DirectHandle<i::Object> object_ref;
  if (function_index < static_cast<int>(module->num_imported_functions)) {
    object_ref = i::direct_handle(
        instance_data->dispatch_table_for_imports()->implicit_arg(
            function_index),
        isolate);
    if (i::Tagged<i::WasmImportData> import_data;
        TryCast(*object_ref, &import_data)) {
      i::Tagged<i::JSFunction> jsfunc =
          i::Cast<i::JSFunction>(import_data->callable());
      i::Tagged<i::Object> data = jsfunc->shared()->GetTrustedData(isolate);
      if (i::Tagged<i::WasmCapiFunctionData> trusted_data;
          TryCast(data, &trusted_data)) {
        return CallWasmCapiFunction(trusted_data, args, results);
      }
      // TODO(jkummerow): Imported and then re-exported JavaScript functions
      // are not supported yet. If we support C-API + JavaScript, we'll need
      // to call those here.
      UNIMPLEMENTED();
    } else {
      // A WasmFunction from another module.
      DCHECK(IsWasmInstanceObject(*object_ref));
    }
  } else {
    // TODO(42204563): Avoid crashing if the instance object is not available.
    CHECK(instance_data->has_instance_object());
    object_ref = direct_handle(instance_data->instance_object(), isolate);
  }

  i::Execution::CallWasm(isolate, wrapper_code, call_target, object_ref,
                         packer.argv());

  if (isolate->has_exception()) {
    i::DirectHandle<i::Object> exception(isolate->exception(), isolate);
    isolate->clear_exception();
    return implement<Trap>::type::make(store,
                                       GetProperException(isolate, exception));
  }

  PopArgs(sig, results, &packer, store);
  return nullptr;
}

i::Address FuncData::v8_callback(i::Address host_data_foreign,
                                 i::Address argv) {
  FuncData* self =
      i::Cast<i::Managed<FuncData>>(i::Tagged<i::Object>(host_data_foreign))
          ->raw();
  StoreImpl* store = impl(self->store);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope scope(isolate);

  isolate->set_context(*v8::Utils::OpenDirectHandle(*store->context()));

  const ownvec<ValType>& param_types = self->type->params();
  const ownvec<ValType>& result_types = self->type->results();

  int num_param_types = static_cast<int>(param_types.size());
  int num_result_types = static_cast<int>(result_types.size());

  auto params = vec<Val>::make_uninitialized(num_param_types);
  auto results = vec<Val>::make_uninitialized(num_result_types);
  i::Address p = argv;
  for (int i = 0; i < num_param_types; ++i) {
    switch (param_types[i]->kind()) {
      case ValKind::I32:
        params[i] = Val(v8::base::ReadUnalignedValue<int32_t>(p));
        p += 4;
        break;
      case ValKind::I64:
        params[i] = Val(v8::base::ReadUnalignedValue<int64_t>(p));
        p += 8;
        break;
      case ValKind::F32:
        params[i] = Val(v8::base::ReadUnalignedValue<float32_t>(p));
        p += 4;
        break;
      case ValKind::F64:
        params[i] = Val(v8::base::ReadUnalignedValue<float64_t>(p));
        p += 8;
        break;
      case ValKind::EXTERNREF:
      case ValKind::FUNCREF: {
        i::Address raw = v8::base::ReadUnalignedValue<i::Address>(p);
        p += sizeof(raw);
        i::DirectHandle<i::Object> obj(i::Tagged<i::Object>(raw), isolate);
        params[i] = Val(V8RefValueToWasm(store, obj));
        break;
      }
    }
  }

  own<Trap> trap;
  if (self->kind == kCallbackWithEnv) {
    trap = self->callback_with_env(self->env, params, results);
  } else {
    trap = self->callback(params, results);
  }

  if (trap) {
    isolate->Throw(*impl(trap.get())->v8_object());
    i::Tagged<i::Object> ex = isolate->exception();
    isolate->clear_exception();
    return ex.ptr();
  }

  p = argv;
  for (int i = 0; i < num_result_types; ++i) {
    switch (result_types[i]->kind()) {
      case ValKind::I32:
        v8::base::WriteUnalignedValue(p, results[i].i32());
        p += 4;
        break;
      case ValKind::I64:
        v8::base::WriteUnalignedValue(p, results[i].i64());
        p += 8;
        break;
      case ValKind::F32:
        v8::base::WriteUnalignedValue(p, results[i].f32());
        p += 4;
        break;
      case ValKind::F64:
        v8::base::WriteUnalignedValue(p, results[i].f64());
        p += 8;
        break;
      case ValKind::EXTERNREF:
      case ValKind::FUNCREF: {
        v8::base::WriteUnalignedValue(
            p, (*WasmRefToV8(isolate, results[i].ref())).ptr());
        p += sizeof(i::Address);
        break;
      }
    }
  }
  return i::kNullAddress;
}

// Global Instances

template <>
struct implement<Global> {
  using type = RefImpl<Global, i::WasmGlobalObject>;
};

WASM_EXPORT void Global::destroy() { delete impl(this); }

WASM_EXPORT auto Global::copy() const -> own<Global> {
  return impl(this)->copy();
}

WASM_EXPORT auto Global::make(Store* store_abs, const GlobalType* type,
                              const Val& val) -> own<Global> {
  StoreImpl* store = impl(store_abs);
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::Isolate* isolate = store->i_isolate();
  i::HandleScope handle_scope(isolate);
  CheckAndHandleInterrupts(isolate);

  DCHECK_EQ(type->content()->kind(), val.kind());

  i::wasm::ValueType i_type = WasmValKindToV8(type->content()->kind());
  bool is_mutable = (type->mutability() == Mutability::VAR);
  const int32_t offset = 0;
  i::DirectHandle<i::WasmGlobalObject> obj =
      i::WasmGlobalObject::New(
          isolate, i::DirectHandle<i::WasmTrustedInstanceData>(),
          i::MaybeDirectHandle<i::JSArrayBuffer>(),
          i::MaybeDirectHandle<i::FixedArray>(), i_type, offset, is_mutable)
          .ToHandleChecked();

  auto global = implement<Global>::type::make(store, obj);
  assert(global);
  global->set(val);
  return global;
}

WASM_EXPORT auto Global::type() const -> own<GlobalType> {
  i::DirectHandle<i::WasmGlobalObject> v8_global = impl(this)->v8_object();
  ValKind kind = V8ValueTypeToWasm(v8_global->type());
  Mutability mutability =
      v8_global->is_mutable() ? Mutability::VAR : Mutability::CONST;
  return GlobalType::make(ValType::make(kind), mutability);
}

WASM_EXPORT auto Global::get() const -> Val {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::DirectHandle<i::WasmGlobalObject> v8_global = impl(this)->v8_object();
  switch (v8_global->type().kind()) {
    case i::wasm::kI32:
      return Val(v8_global->GetI32());
    case i::wasm::kI64:
      return Val(v8_global->GetI64());
    case i::wasm::kF32:
      return Val(v8_global->GetF32());
    case i::wasm::kF64:
      return Val(v8_global->GetF64());
    case i::wasm::kRef:
    case i::wasm::kRefNull: {
      // TODO(14034): Handle types other than funcref and externref if needed.
      StoreImpl* store = impl(this)->store();
      i::HandleScope scope(store->i_isolate());
      i::DirectHandle<i::Object> result = v8_global->GetRef();
      if (IsWasmFuncRef(*result)) {
        result = i::WasmInternalFunction::GetOrCreateExternal(i::direct_handle(
            i::Cast<i::WasmFuncRef>(*result)->internal(store->i_isolate()),
            store->i_isolate()));
      }
      if (IsWasmNull(*result)) {
        result = i::Isolate::Current()->factory()->null_value();
      }
      return Val(V8RefValueToWasm(store, result));
    }
    case i::wasm::kS128:
      // TODO(14034): Implement these.
      UNIMPLEMENTED();
    case i::wasm::kI8:
    case i::wasm::kI16:
    case i::wasm::kF16:
    case i::wasm::kVoid:
    case i::wasm::kTop:
    case i::wasm::kBottom:
      UNREACHABLE();
  }
}

WASM_EXPORT void Global::set(const Val& val) {
  v8::Isolate::Scope isolate_scope(impl(this)->store()->isolate());
  i::DirectHandle<i::WasmGlobalObject> v8_global = impl(this)->v8_object();
  switch (val.kind()) {
    case ValKind::I32:
      return v8_global->SetI32(val.i32());
    case ValKind::I64:
      return v8_global->SetI64(val.i64());
    case ValKind::F32:
      return v8_global->SetF32(val.f32());
    case ValKind::F64:
      return v8_global->SetF64(val.f64());
    case ValKind::EXTERNREF:
      return v8_global->SetRef(
          WasmRefToV8(impl(this)->store()->i_isolate(), val.ref()));
    case ValKind::FUNCREF: {
      i::Isolate* isolate = impl(this)->store()->i_isolate();
      auto external = WasmRefToV8(impl(this)->store()->i_isolate(), val.ref());
      const char* error_message;
      auto internal = i::wasm::JSToWasmObject(isolate, nullptr, external,
                                              v8_global->type(), &error_message)
                          .ToHandleChecked();
      v8_global->SetRef(internal);
      return;
    }
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
}

// Table Instances

template <>
struct implement<Table> {
  using type = RefImpl<Table, i::WasmTableObject>;
};

WASM_EXPORT void Table::destroy() { delete impl(this); }

WASM_EXPORT auto Table::copy() const -> own<Table> {
  return impl(this)->copy();
}

WASM_EXPORT auto Table::make(Store* store_abs, const TableType* type,
                             const Ref* ref) -> own<Table> {
  StoreImpl* store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope scope(isolate);
  CheckAndHandleInterrupts(isolate);

  // Get "element".
  i::wasm::ValueType i_type;
  i::wasm::CanonicalValueType canonical_type;
  switch (type->element()->kind()) {
    case ValKind::FUNCREF:
      i_type = i::wasm::kWasmFuncRef;
      canonical_type = i::wasm::kWasmFuncRef;
      break;
    case ValKind::EXTERNREF:
      // See Engine::make().
      i_type = i::wasm::kWasmExternRef;
      canonical_type = i::wasm::kWasmExternRef;
      break;
    default:
      UNREACHABLE();
  }

  const Limits& limits = type->limits();
  uint32_t minimum = limits.min;
  if (minimum > i::wasm::max_table_init_entries()) return nullptr;
  uint32_t maximum = limits.max;
  bool has_maximum = false;
  if (maximum != Limits(0).max) {
    has_maximum = true;
    if (maximum < minimum) return nullptr;
    if (maximum > i::wasm::max_table_init_entries()) return nullptr;
  }

  i::DirectHandle<i::WasmTableObject> table_obj = i::WasmTableObject::New(
      isolate, i::DirectHandle<i::WasmTrustedInstanceData>(), i_type,
      canonical_type, minimum, has_maximum, maximum,
      isolate->factory()->null_value(), i::wasm::AddressType::kI32);

  if (ref) {
    i::DirectHandle<i::JSReceiver> init = impl(ref)->v8_object();
    for (uint32_t i = 0; i < minimum; i++) {
      table_obj->Set(isolate, table_obj, i, init);
    }
  }
  return implement<Table>::type::make(store, table_obj);
}

WASM_EXPORT auto Table::type() const -> own<TableType> {
  i::DirectHandle<i::WasmTableObject> table = impl(this)->v8_object();
  uint32_t min = table->current_length();
  // Note: The C-API is not updated for memory64 yet; limits use uint32_t. Thus
  // truncate the actual declared maximum to kMaxUint32.
  uint32_t max = static_cast<uint32_t>(std::min<uint64_t>(
      i::kMaxUInt32, table->maximum_length_u64().value_or(i::kMaxUInt32)));
  ValKind kind;
  switch (table->unsafe_type().raw_bit_field()) {
    case i::wasm::kWasmFuncRef.raw_bit_field():
      kind = ValKind::FUNCREF;
      break;
    case i::wasm::kWasmExternRef.raw_bit_field():
      kind = ValKind::EXTERNREF;
      break;
    default:
      UNREACHABLE();
  }
  return TableType::make(ValType::make(kind), Limits(min, max));
}

// TODO(14034): Handle types other than funcref and externref if needed.
WASM_EXPORT auto Table::get(size_t index) const -> own<Ref> {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::DirectHandle<i::WasmTableObject> table = impl(this)->v8_object();
  if (index >= static_cast<size_t>(table->current_length())) return own<Ref>();
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  i::HandleScope handle_scope(isolate);
  i::DirectHandle<i::Object> result =
      i::WasmTableObject::Get(isolate, table, static_cast<uint32_t>(index));
  if (IsWasmFuncRef(*result)) {
    result = i::WasmInternalFunction::GetOrCreateExternal(i::direct_handle(
        i::Cast<i::WasmFuncRef>(*result)->internal(isolate), isolate));
  }
  if (IsWasmNull(*result)) {
    result = isolate->factory()->null_value();
  }
  DCHECK(IsNull(*result, isolate) || IsJSReceiver(*result));
  return V8RefValueToWasm(impl(this)->store(), result);
}

WASM_EXPORT auto Table::set(size_t index, const Ref* ref) -> bool {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::DirectHandle<i::WasmTableObject> table = impl(this)->v8_object();
  if (index >= static_cast<size_t>(table->current_length())) return false;
  i::HandleScope handle_scope(isolate);
  i::DirectHandle<i::Object> obj = WasmRefToV8(isolate, ref);
  const char* error_message;
  // We can use `table->unsafe_type()` and `module == nullptr` here as long
  // as the C-API doesn't support indexed types.
  DCHECK(!table->unsafe_type().has_index());
  i::DirectHandle<i::Object> obj_as_wasm =
      i::wasm::JSToWasmObject(isolate, nullptr, obj, table->unsafe_type(),
                              &error_message)
          .ToHandleChecked();
  i::WasmTableObject::Set(isolate, table, static_cast<uint32_t>(index),
                          obj_as_wasm);
  return true;
}

// TODO(jkummerow): Having Table::size_t shadowing "std" size_t is ugly.
WASM_EXPORT auto Table::size() const -> size_t {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  return impl(this)->v8_object()->current_length();
}

WASM_EXPORT auto Table::grow(size_t delta, const Ref* ref) -> bool {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::DirectHandle<i::WasmTableObject> table = impl(this)->v8_object();
  i::HandleScope scope(isolate);
  i::DirectHandle<i::Object> obj = WasmRefToV8(isolate, ref);
  const char* error_message;
  // We can use `table->unsafe_type()` and `module == nullptr` here as long
  // as the C-API doesn't support indexed types.
  DCHECK(!table->unsafe_type().has_index());
  i::DirectHandle<i::Object> obj_as_wasm =
      i::wasm::JSToWasmObject(isolate, nullptr, obj, table->unsafe_type(),
                              &error_message)
          .ToHandleChecked();
  int result = i::WasmTableObject::Grow(
      isolate, table, static_cast<uint32_t>(delta), obj_as_wasm);
  return result >= 0;
}

// Memory Instances

template <>
struct implement<Memory> {
  using type = RefImpl<Memory, i::WasmMemoryObject>;
};

WASM_EXPORT void Memory::destroy() { delete impl(this); }

WASM_EXPORT auto Memory::copy() const -> own<Memory> {
  return impl(this)->copy();
}

WASM_EXPORT auto Memory::make(Store* store_abs, const MemoryType* type)
    -> own<Memory> {
  StoreImpl* store = impl(store_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope scope(isolate);
  CheckAndHandleInterrupts(isolate);

  const Limits& limits = type->limits();
  uint32_t minimum = limits.min;
  // The max_mem_pages limit is only spec'ed for JS embeddings, so we'll
  // directly use the maximum pages limit here.
  if (minimum > i::wasm::kSpecMaxMemory32Pages) return nullptr;
  uint32_t maximum = limits.max;
  if (maximum != Limits(0).max) {
    if (maximum < minimum) return nullptr;
    if (maximum > i::wasm::kSpecMaxMemory32Pages) return nullptr;
  }
  // TODO(wasm+): Support shared memory and memory64.
  i::SharedFlag shared = i::SharedFlag::kNotShared;
  i::wasm::AddressType address_type = i::wasm::AddressType::kI32;
  i::DirectHandle<i::WasmMemoryObject> memory_obj;
  if (!i::WasmMemoryObject::New(isolate, minimum, maximum, shared, address_type)
           .ToHandle(&memory_obj)) {
    return own<Memory>();
  }
  return implement<Memory>::type::make(store, memory_obj);
}

WASM_EXPORT auto Memory::type() const -> own<MemoryType> {
  PtrComprCageAccessScope ptr_compr_cage_access_scope(impl(this)->isolate());
  i::DirectHandle<i::WasmMemoryObject> memory = impl(this)->v8_object();
  uint32_t min = static_cast<uint32_t>(memory->array_buffer()->byte_length() /
                                       i::wasm::kWasmPageSize);
  uint32_t max =
      memory->has_maximum_pages() ? memory->maximum_pages() : 0xFFFFFFFFu;
  return MemoryType::make(Limits(min, max));
}

WASM_EXPORT auto Memory::data() const -> byte_t* {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  return reinterpret_cast<byte_t*>(
      impl(this)->v8_object()->array_buffer()->backing_store());
}

WASM_EXPORT auto Memory::data_size() const -> size_t {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  return impl(this)->v8_object()->array_buffer()->byte_length();
}

WASM_EXPORT auto Memory::size() const -> pages_t {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  PtrComprCageAccessScope ptr_compr_cage_access_scope(isolate);
  return static_cast<pages_t>(
      impl(this)->v8_object()->array_buffer()->byte_length() /
      i::wasm::kWasmPageSize);
}

WASM_EXPORT auto Memory::grow(pages_t delta) -> bool {
  i::Isolate* isolate = impl(this)->isolate();
  v8::Isolate::Scope isolate_scope(reinterpret_cast<v8::Isolate*>(isolate));
  i::HandleScope handle_scope(isolate);
  i::DirectHandle<i::WasmMemoryObject> memory = impl(this)->v8_object();
  int32_t old = i::WasmMemoryObject::Grow(isolate, memory, delta);
  return old != -1;
}

// Module Instances

template <>
struct implement<Instance> {
  using type = RefImpl<Instance, i::WasmInstanceObject>;
};

WASM_EXPORT void Instance::destroy() { delete impl(this); }
WASM_EXPORT auto Instance::copy() const -> own<Instance> {
  return impl(this)->copy();
}

WASM_EXPORT own<Instance> Instance::make(Store* store_abs,
                                         const Module* module_abs,
                                         const vec<Extern*>& imports,
                                         own<Trap>* trap) {
  StoreImpl* store = impl(store_abs);
  const implement<Module>::type* module = impl(module_abs);
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  CheckAndHandleInterrupts(isolate);

  if (trap) *trap = nullptr;
  ownvec<ImportType> import_types = module_abs->imports();
  i::DirectHandle<i::JSObject> imports_obj =
      isolate->factory()->NewJSObject(isolate->object_function());
  for (size_t i = 0; i < import_types.size(); ++i) {
    ImportType* type = import_types[i].get();
    i::DirectHandle<i::String> module_str =
        VecToString(isolate, type->module());
    i::DirectHandle<i::String> name_str = VecToString(isolate, type->name());

    i::DirectHandle<i::JSObject> module_obj;
    i::LookupIterator module_it(isolate, imports_obj, module_str,
                                i::LookupIterator::OWN_SKIP_INTERCEPTOR);
    if (i::JSObject::HasProperty(&module_it).ToChecked()) {
      module_obj = i::Cast<i::JSObject>(
          i::Object::GetProperty(&module_it).ToHandleChecked());
    } else {
      module_obj = isolate->factory()->NewJSObject(isolate->object_function());
      ignore(
          i::Object::SetProperty(isolate, imports_obj, module_str, module_obj));
    }
    ignore(i::Object::SetProperty(isolate, module_obj, name_str,
                                  impl(imports[i])->v8_object()));
  }
  i::wasm::ErrorThrower thrower(isolate, "instantiation");
  i::MaybeDirectHandle<i::WasmInstanceObject> instance_obj =
      i::wasm::GetWasmEngine()->SyncInstantiate(
          isolate, &thrower, module->v8_object(), imports_obj,
          i::MaybeDirectHandle<i::JSArrayBuffer>());
  if (trap) {
    if (thrower.error()) {
      *trap = implement<Trap>::type::make(
          store, GetProperException(isolate, thrower.Reify()));
      DCHECK(!thrower.error());           // Reify() called Reset().
      DCHECK(!isolate->has_exception());  // Hasn't been thrown yet.
      return own<Instance>();
    } else if (isolate->has_exception()) {
      i::DirectHandle<i::Object> maybe_exception(isolate->exception(), isolate);
      *trap = implement<Trap>::type::make(
          store, GetProperException(isolate, maybe_exception));
      isolate->clear_exception();
      return own<Instance>();
    }
  } else if (instance_obj.is_null()) {
    // If no {trap} output is specified, silently swallow all errors.
    thrower.Reset();
    isolate->clear_exception();
    return own<Instance>();
  }
  return implement<Instance>::type::make(store, instance_obj.ToHandleChecked());
}

namespace {

own<Instance> GetInstance(StoreImpl* store,
                          i::DirectHandle<i::WasmInstanceObject> instance) {
  return implement<Instance>::type::make(store, instance);
}

}  // namespace

WASM_EXPORT auto Instance::exports() const -> ownvec<Extern> {
  const implement<Instance>::type* instance = impl(this);
  StoreImpl* store = instance->store();
  i::Isolate* isolate = store->i_isolate();
  v8::Isolate::Scope isolate_scope(store->isolate());
  i::HandleScope handle_scope(isolate);
  CheckAndHandleInterrupts(isolate);
  i::DirectHandle<i::WasmInstanceObject> instance_obj = instance->v8_object();
  i::DirectHandle<i::WasmModuleObject> module_obj(instance_obj->module_object(),
                                                  isolate);
  i::DirectHandle<i::JSObject> exports_obj(instance_obj->exports_object(),
                                           isolate);

  ownvec<ExportType> export_types = ExportsImpl(module_obj);
  ownvec<Extern> exports =
      ownvec<Extern>::make_uninitialized(export_types.size());
  if (!exports) return ownvec<Extern>::invalid();

  for (size_t i = 0; i < export_types.size(); ++i) {
    auto& name = export_types[i]->name();
    i::DirectHandle<i::String> name_str = VecToString(isolate, name);
    i::DirectHandle<i::Object> obj =
        i::Object::GetProperty(isolate, exports_obj, name_str)
            .ToHandleChecked();

    const ExternType* type = export_types[i]->type();
    switch (type->kind()) {
      case ExternKind::FUNC: {
        DCHECK(i::WasmExternalFunction::IsWasmExternalFunction(*obj));
        exports[i] = implement<Func>::type::make(
            store, i::Cast<i::WasmExternalFunction>(obj));
      } break;
      case ExternKind::GLOBAL: {
        exports[i] = implement<Global>::type::make(
            store, i::Cast<i::WasmGlobalObject>(obj));
      } break;
      case ExternKind::TABLE: {
        exports[i] = implement<Table>::type::make(
            store, i::Cast<i::WasmTableObject>(obj));
      } break;
      case ExternKind::MEMORY: {
        exports[i] = implement<Memory>::type::make(
            store, i::Cast<i::WasmMemoryObject>(obj));
      } break;
    }
  }

  return exports;
}

///////////////////////////////////////////////////////////////////////////////

}  // namespace wasm

// BEGIN FILE wasm-c.cc

extern "C" {

///////////////////////////////////////////////////////////////////////////////
// Auxiliaries

// Backing implementation

extern "C++" {

template <class T>
struct borrowed_vec {
  wasm::vec<T> it;
  explicit borrowed_vec(wasm::vec<T>&& v) : it(std::move(v)) {}
  borrowed_vec(borrowed_vec<T>&& that) : it(std::move(that.it)) {}
  ~borrowed_vec() { it.release(); }
};

}  // extern "C++"

#define WASM_DEFINE_OWN(name, Name)                                            \
  struct wasm_##name##_t : Name {};                                            \
                                                                               \
  void wasm_##name##_delete(wasm_##name##_t* x) { delete x; }                  \
                                                                               \
  extern "C++" inline auto hide_##name(Name* x)->wasm_##name##_t* {            \
    return static_cast<wasm_##name##_t*>(x);                                   \
  }                                                                            \
  extern "C++" inline auto hide_##name(const Name* x)                          \
      ->const wasm_##name##_t* {                                               \
    return static_cast<const wasm_##name##_t*>(x);                             \
  }                                                                            \
  extern "C++" inline auto reveal_##name(wasm_##name##_t* x)->Name* {          \
    return x;                                                                  \
  }                                                                            \
  extern "C++" inline auto reveal_##name(const wasm_##name##_t* x)             \
      ->const Name* {                                                          \
    return x;                                                                  \
  }                                                                            \
  extern "C++" inline auto get_##name(wasm::own<Name>& x)->wasm_##name##_t* {  \
    return hide_##name(x.get());                                               \
  }                                                                            \
  extern "C++" inline auto get_##name(const wasm::own<Name>& x)                \
      ->const wasm_##name##_t* {                                               \
    return hide_##name(x.get());                                               \
  }                                                                            \
  extern "C++" inline auto release_##name(wasm::own<Name>&& x)                 \
      ->wasm_##name##_t* {                                                     \
    return hide_##name(x.release());                                           \
  }                                                                            \
  extern "C++" inline auto adopt_##name(wasm_##name##_t* x)->wasm::own<Name> { \
    return make_own(x);                                                        \
  }

// Vectors

#ifdef V8_GC_MOLE
#define ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none)

#else
#define ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none)                 \
  static_assert(sizeof(wasm_##name##_vec_t) == sizeof(vec<Name>),          \
                "C/C++ incompatibility");                                  \
  static_assert(                                                           \
      sizeof(wasm_##name##_t ptr_or_none) == sizeof(vec<Name>::elem_type), \
      "C/C++ incompatibility");
#endif

#define WASM_DEFINE_VEC_BASE(name, Name, vec, ptr_or_none)                     \
  ASSERT_VEC_BASE_SIZE(name, Name, vec, ptr_or_none)                           \
  extern "C++" inline auto hide_##name##_vec(vec<Name>& v)                     \
      ->wasm_##name##_vec_t* {                                                 \
    return reinterpret_cast<wasm_##name##_vec_t*>(&v);                         \
  }                                                                            \
  extern "C++" inline auto hide_##name##_vec(const vec<Name>& v)               \
      ->const wasm_##name##_vec_t* {                                           \
    return reinterpret_cast<const wasm_##name##_vec_t*>(&v);                   \
  }                                                                            \
  extern "C++" inline auto hide_##name##_vec(vec<Name>::elem_type* v)          \
      ->wasm_##name##_t ptr_or_none* {                                         \
    return reinterpret_cast<wasm_##name##_t ptr_or_none*>(v);                  \
  }                                                                            \
  extern "C++" inline auto hide_##name##_vec(const vec<Name>::elem_type* v)    \
      ->wasm_##name##_t ptr_or_none const* {                                   \
    return reinterpret_cast<wasm_##name##_t ptr_or_none const*>(v);            \
  }                                                                            \
  extern "C++" inline auto reveal_##name##_vec(wasm_##name##_t ptr_or_none* v) \
      ->vec<Name>::elem_type* {                                                \
    return reinterpret_cast<vec<Name>::elem_type*>(v);                         \
  }                                                                            \
  extern "C++" inline auto reveal_##name##_vec(                                \
      wasm_##name##_t ptr_or_none const* v)                                    \
      ->const vec<Name>::elem_type* {                                          \
    return reinterpret_cast<const vec<Name>::elem_type*>(v);                   \
  }                                                                            \
  extern "C++" inline auto get_##name##_vec(vec<Name>& v)                      \
      ->wasm_##name##_vec_t {                                                  \
    wasm_##name##_vec_t v2 = {v.size(), hide_##name##_vec(v.get())};           \
    return v2;                                                                 \
  }                                                                            \
  extern "C++" inline auto get_##name##_vec(const vec<Name>& v)                \
      ->const wasm_##name##_vec_t {                                            \
    wasm_##name##_vec_t v2 = {                                                 \
        v.size(),                                                              \
        const_cast<wasm_##name##_t ptr_or_none*>(hide_##name##_vec(v.get()))}; \
    return v2;                                                                 \
  }                                                                            \
  extern "C++" inline auto release_##name##_vec(vec<Name>&& v)                 \
      ->wasm_##name##_vec_t {                                                  \
    wasm_##name##_vec_t v2 = {v.size(), hide_##name##_vec(v.release())};       \
    return v2;                                                                 \
  }                                                                            \
  extern "C++" inline auto adopt_##name##_vec(wasm_##name##_vec_t* v)          \
      ->vec<Name> {                                                            \
    return vec<Name>::adopt(v->size, reveal_##name##_vec(v->data));            \
  }                                                                            \
  extern "C++" inline auto adopt_##name##_vec(const wasm_##name##_vec_t* v)    \
      ->const vec<Name> {                                                      \
    return vec<Name>::adopt(v->size, reveal_##name##_vec(v->data));            \
  }                                                                            \
  extern "C++" inline auto borrow_##name##_vec(const wasm_##name##_vec_t* v)   \
      ->borrowed_vec<vec<Name>::elem_type> {                                   \
    return borrowed_vec<vec<Name>::elem_type>(                                 \
        vec<Name>::adopt(v->size, reveal_##name##_vec(v->data)));              \
  }                                                                            \
                                                                               \
  void wasm_##name##_vec_new_uninitialized(wasm_##name##_vec_t* out,           \
                                           size_t size) {                      \
    *out = release_##name##_vec(vec<Name>::make_uninitialized(size));          \
  }                                                                            \
  void wasm_##name##_vec_new_empty(wasm_##name##_vec_t* out) {                 \
    wasm_##name##_vec_new_uninitialized(out, 0);                               \
  }                                                                            \
                                                                               \
  void wasm_##name##_vec_delete(wasm_##name##_vec_t* v) {                      \
    adopt_##name##_vec(v);                                                     \
  }

// Vectors with no ownership management of elements
#define WASM_DEFINE_VEC_PLAIN(name, Name)                                     \
  WASM_DEFINE_VEC_BASE(name, Name,                                            \
                       wasm::vec, ) /* NOLINT(whitespace/parens) */           \
                                                                              \
  void wasm_##name##_vec_new(wasm_##name##_vec_t* out, size_t size,           \
                             const wasm_##name##_t data[]) {                  \
    auto v2 = wasm::vec<Name>::make_uninitialized(size);                      \
    if (v2.size() != 0) {                                                     \
      memcpy(v2.get(), data, size * sizeof(wasm_##name##_t));                 \
    }                                                                         \
    *out = release_##name##_vec(std::move(v2));                               \
  }                                                                           \
                                                                              \
  WASM_API_EXTERN void wasm_##name##_vec_copy(wasm_##name##_vec_t* out,       \
                                              const wasm_##name##_vec_t* v) { \
    wasm_##name##_vec_new(out, v->size, v->data);                             \
  }

// Vectors that own their elements
#define WASM_DEFINE_VEC_OWN(name, Name)                                       \
  WASM_DEFINE_VEC_BASE(name, Name, wasm::ownvec, *)                           \
                                                                              \
  void wasm_##name##_vec_new(wasm_##name##_vec_t* out, size_t size,           \
                             wasm_##name##_t* const data[]) {                 \
    auto v2 = wasm::ownvec<Name>::make_uninitialized(size);                   \
    for (size_t i = 0; i < v2.size(); ++i) {                                  \
      v2[i] = adopt_##name(data[i]);                                          \
    }                                                                         \
    *out = release_##name##_vec(std::move(v2));                               \
  }                                                                           \
                                                                              \
  WASM_API_EXTERN void wasm_##name##_vec_copy(wasm_##name##_vec_t* out,       \
                                              const wasm_##name##_vec_t* v) { \
    auto v2 = wasm::ownvec<Name>::make_uninitialized(v->size);                \
    for (size_t i = 0; i < v2.size(); ++i) {                                  \
      v2[i] = adopt_##name(wasm_##name##_copy(v->data[i]));                   \
    }                                                                         \
    *out = release_##name##_vec(std::move(v2));                               \
  }

extern "C++" {
template <class T>
inline auto is_empty(T* p) -> bool {
  return !p;
}
}

// Byte vectors

WASM_DEFINE_VEC_PLAIN(byte, byte_t)

///////////////////////////////////////////////////////////////////////////////
// Runtime Environment

// Configuration

WASM_DEFINE_OWN(config, wasm::Config)

wasm_config_t* wasm_config_new() {
  return release_config(wasm::Config::make());
}

// Engine

WASM_DEFINE_OWN(engine, wasm::Engine)

wasm_engine_t* wasm_engine_new() {
  return release_engine(wasm::Engine::make());
}

wasm_engine_t* wasm_engine_new_with_config(wasm_config_t* config) {
  return release_engine(wasm::Engine::make(adopt_config(config)));
}

// Stores

WASM_DEFINE_OWN(store, wasm::Store)

wasm_store_t* wasm_store_new(wasm_engine_t* engine) {
  return release_store(wasm::Store::make(engine));
}

///////////////////////////////////////////////////////////////////////////////
// Type Representations

// Type attributes

extern "C++" inline auto hide_mutability(wasm::Mutability mutability)
    -> wasm_mutability_t {
  return static_cast<wasm_mutability_t>(mutability);
}

extern "C++" inline auto reveal_mutability(wasm_mutability_t mutability)
    -> wasm::Mutability {
  return static_cast<wasm::Mutability>(mutability);
}

extern "C++" inline auto hide_limits(const wasm::Limits& limits)
    -> const wasm_limits_t* {
  return reinterpret_cast<const wasm_limits_t*>(&limits);
}

extern "C++" inline auto reveal_limits(wasm_limits_t limits) -> wasm::Limits {
  return wasm::Limits(limits.min, limits.max);
}

extern "C++" inline auto hide_valkind(wasm::ValKind kind) -> wasm_valkind_t {
  return static_cast<wasm_valkind_t>(kind);
}

extern "C++" inline auto reveal_valkind(wasm_valkind_t kind) -> wasm::ValKind {
  return static_cast<wasm::ValKind>(kind);
}

extern "C++" inline auto hide_externkind(wasm::ExternKind kind)
    -> wasm_externkind_t {
  return static_cast<wasm_externkind_t>(kind);
}

extern "C++" inline auto reveal_externkind(wasm_externkind_t kind)
    -> wasm::ExternKind {
  return static_cast<wasm::ExternKind>(kind);
}

// Generic

#define WASM_DEFINE_TYPE(name, Name)                   \
  WASM_DEFINE_OWN(name, Name)                          \
  WASM_DEFINE_VEC_OWN(name, Name)                      \
                                                       \
  WASM_API_EXTERN wasm_##name##_t* wasm_##name##_copy( \
      const wasm_##name##_t* t) {                      \
    return release_##name(t->copy());                  \
  }

// Value Types

WASM_DEFINE_TYPE(valtype, wasm::ValType)

wasm_valtype_t* wasm_valtype_new(wasm_valkind_t k) {
  return release_valtype(wasm::ValType::make(reveal_valkind(k)));
}

wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t* t) {
  return hide_valkind(t->kind());
}

// Function Types

WASM_DEFINE_TYPE(functype, wasm::FuncType)

wasm_functype_t* wasm_functype_new(wasm_valtype_vec_t* params,
                                   wasm_valtype_vec_t* results) {
  return release_functype(wasm::FuncType::make(adopt_valtype_vec(params),
                                               adopt_valtype_vec(results)));
}

const wasm_valtype_vec_t* wasm_functype_params(const wasm_functype_t* ft) {
  return hide_valtype_vec(ft->params());
}

const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t* ft) {
  return hide_valtype_vec(ft->results());
}

// Global Types

WASM_DEFINE_TYPE(globaltype, wasm::GlobalType)

wasm_globaltype_t* wasm_globaltype_new(wasm_valtype_t* content,
                                       wasm_mutability_t mutability) {
  return release_globaltype(wasm::GlobalType::make(
      adopt_valtype(content), reveal_mutability(mutability)));
}

const wasm_valtype_t* wasm_globaltype_content(const wasm_globaltype_t* gt) {
  return hide_valtype(gt->content());
}

wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t* gt) {
  return hide_mutability(gt->mutability());
}

// Table Types

WASM_DEFINE_TYPE(tabletype, wasm::TableType)

wasm_tabletype_t* wasm_tabletype_new(wasm_valtype_t* element,
                                     const wasm_limits_t* limits) {
  return release_tabletype(
      wasm::TableType::make(adopt_valtype(element), reveal_limits(*limits)));
}

const wasm_valtype_t* wasm_tabletype_element(const wasm_tabletype_t* tt) {
  return hide_valtype(tt->element());
}

const wasm_limits_t* wasm_tabletype_limits(const wasm_tabletype_t* tt) {
  return hide_limits(tt->limits());
}

// Memory Types

WASM_DEFINE_TYPE(memorytype, wasm::MemoryType)

wasm_memorytype_t* wasm_memorytype_new(const wasm_limits_t* limits) {
  return release_memorytype(wasm::MemoryType::make(reveal_limits(*limits)));
}

const wasm_limits_t* wasm_memorytype_limits(const wasm_memorytype_t* mt) {
  return hide_limits(mt->limits());
}

// Extern Types

WASM_DEFINE_TYPE(externtype, wasm::ExternType)

wasm_externkind_t wasm_externtype_kind(const wasm_externtype_t* et) {
  return hide_externkind(et->kind());
}

wasm_externtype_t* wasm_functype_as_externtype(wasm_functype_t* ft) {
  return hide_externtype(static_cast<wasm::ExternType*>(ft));
}
wasm_externtype_t* wasm_globaltype_as_externtype(wasm_globaltype_t* gt) {
  return hide_externtype(static_cast<wasm::ExternType*>(gt));
}
wasm_externtype_t* wasm_tabletype_as_externtype(wasm_tabletype_t* tt) {
  return hide_externtype(static_cast<wasm::ExternType*>(tt));
}
wasm_externtype_t* wasm_memorytype_as_externtype(wasm_memorytype_t* mt) {
  return hide_externtype(static_cast<wasm::ExternType*>(mt));
}

const wasm_externtype_t* wasm_functype_as_externtype_const(
    const wasm_functype_t* ft) {
  return hide_externtype(static_cast<const wasm::ExternType*>(ft));
}
const wasm_externtype_t* wasm_globaltype_as_externtype_const(
    const wasm_globaltype_t* gt) {
  return hide_externtype(static_cast<const wasm::ExternType*>(gt));
}
const wasm_externtype_t* wasm_tabletype_as_externtype_const(
    const wasm_tabletype_t* tt) {
  return hide_externtype(static_cast<const wasm::ExternType*>(tt));
}
const wasm_externtype_t* wasm_memorytype_as_externtype_const(
    const wasm_memorytype_t* mt) {
  return hide_externtype(static_cast<const wasm::ExternType*>(mt));
}

wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::FUNC
             ? hide_functype(
                   static_cast<wasm::FuncType*>(reveal_externtype(et)))
             : nullptr;
}
wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::GLOBAL
             ? hide_globaltype(
                   static_cast<wasm::GlobalType*>(reveal_externtype(et)))
             : nullptr;
}
wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::TABLE
             ? hide_tabletype(
                   static_cast<wasm::TableType*>(reveal_externtype(et)))
             : nullptr;
}
wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::MEMORY
             ? hide_memorytype(
                   static_cast<wasm::MemoryType*>(reveal_externtype(et)))
             : nullptr;
}

const wasm_functype_t* wasm_externtype_as_functype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::FUNC
             ? hide_functype(
                   static_cast<const wasm::FuncType*>(reveal_externtype(et)))
             : nullptr;
}
const wasm_globaltype_t* wasm_externtype_as_globaltype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::GLOBAL
             ? hide_globaltype(
                   static_cast<const wasm::GlobalType*>(reveal_externtype(et)))
             : nullptr;
}
const wasm_tabletype_t* wasm_externtype_as_tabletype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::TABLE
             ? hide_tabletype(
                   static_cast<const wasm::TableType*>(reveal_externtype(et)))
             : nullptr;
}
const wasm_memorytype_t* wasm_externtype_as_memorytype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::ExternKind::MEMORY
             ? hide_memorytype(
                   static_cast<const wasm::MemoryType*>(reveal_externtype(et)))
             : nullptr;
}

// Import Types

WASM_DEFINE_TYPE(importtype, wasm::ImportType)

wasm_importtype_t* wasm_importtype_new(wasm_name_t* module, wasm_name_t* name,
                                       wasm_externtype_t* type) {
  return release_importtype(wasm::ImportType::make(
      adopt_byte_vec(module), adopt_byte_vec(name), adopt_externtype(type)));
}

const wasm_name_t* wasm_importtype_module(const wasm_importtype_t* it) {
  return hide_byte_vec(it->module());
}

const wasm_name_t* wasm_importtype_name(const wasm_importtype_t* it) {
  return hide_byte_vec(it->name());
}

const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t* it) {
  return hide_externtype(it->type());
}

// Export Types

WASM_DEFINE_TYPE(exporttype, wasm::ExportType)

wasm_exporttype_t* wasm_exporttype_new(wasm_name_t* name,
                                       wasm_externtype_t* type) {
  return release_exporttype(
      wasm::ExportType::make(adopt_byte_vec(name), adopt_externtype(type)));
}

const wasm_name_t* wasm_exporttype_name(const wasm_exporttype_t* et) {
  return hide_byte_vec(et->name());
}

const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t* et) {
  return hide_externtype(et->type());
}

///////////////////////////////////////////////////////////////////////////////
// Runtime Values

// References

#define WASM_DEFINE_REF_BASE(name, Name)                             \
  WASM_DEFINE_OWN(name, Name)                                        \
                                                                     \
  wasm_##name##_t* wasm_##name##_copy(const wasm_##name##_t* t) {    \
    return release_##name(t->copy());                                \
  }                                                                  \
                                                                     \
  bool wasm_##name##_same(const wasm_##name##_t* t1,                 \
                          const wasm_##name##_t* t2) {               \
    return t1->same(t2);                                             \
  }                                                                  \
                                                                     \
  void* wasm_##name##_get_host_info(const wasm_##name##_t* r) {      \
    return r->get_host_info();                                       \
  }                                                                  \
  void wasm_##name##_set_host_info(wasm_##name##_t* r, void* info) { \
    r->set_host_info(info);                                          \
  }                                                                  \
  void wasm_##name##_set_host_info_with_finalizer(                   \
      wasm_##name##_t* r, void* info, void (*finalizer)(void*)) {    \
    r->set_host_info(info, finalizer);                               \
  }

#define WASM_DEFINE_REF(name, Name)                                        \
  WASM_DEFINE_REF_BASE(name, Name)                                         \
                                                                           \
  wasm_ref_t* wasm_##name##_as_ref(wasm_##name##_t* r) {                   \
    return hide_ref(static_cast<wasm::Ref*>(reveal_##name(r)));            \
  }                                                                        \
  wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t* r) {                     \
    return hide_##name(static_cast<Name*>(reveal_ref(r)));                 \
  }                                                                        \
                                                                           \
  const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t* r) { \
    return hide_ref(static_cast<const wasm::Ref*>(reveal_##name(r)));      \
  }                                                                        \
  const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t* r) { \
    return hide_##name(static_cast<const Name*>(reveal_ref(r)));           \
  }

#define WASM_DEFINE_SHARABLE_REF(name, Name) \
  WASM_DEFINE_REF(name, Name)                \
  WASM_DEFINE_OWN(shared_##name, wasm::Shared<Name>)

WASM_DEFINE_REF_BASE(ref, wasm::Ref)

// Values

extern "C++" {

inline auto is_empty(wasm_val_t v) -> bool {
  return !is_ref(reveal_valkind(v.kind)) || !v.of.ref;
}

inline auto hide_val(wasm::Val v) -> wasm_val_t {
  wasm_val_t v2 = {hide_valkind(v.kind()), {}};
  switch (v.kind()) {
    case wasm::ValKind::I32:
      v2.of.i32 = v.i32();
      break;
    case wasm::ValKind::I64:
      v2.of.i64 = v.i64();
      break;
    case wasm::ValKind::F32:
      v2.of.f32 = v.f32();
      break;
    case wasm::ValKind::F64:
      v2.of.f64 = v.f64();
      break;
    case wasm::ValKind::EXTERNREF:
    case wasm::ValKind::FUNCREF:
      v2.of.ref = hide_ref(v.ref());
      break;
    default:
      UNREACHABLE();
  }
  return v2;
}

inline auto release_val(wasm::Val v) -> wasm_val_t {
  wasm_val_t v2 = {hide_valkind(v.kind()), {}};
  switch (v.kind()) {
    case wasm::ValKind::I32:
      v2.of.i32 = v.i32();
      break;
    case wasm::ValKind::I64:
      v2.of.i64 = v.i64();
      break;
    case wasm::ValKind::F32:
      v2.of.f32 = v.f32();
      break;
    case wasm::ValKind::F64:
      v2.of.f64 = v.f64();
      break;
    case wasm::ValKind::EXTERNREF:
    case wasm::ValKind::FUNCREF:
      v2.of.ref = release_ref(v.release_ref());
      break;
    default:
      UNREACHABLE();
  }
  return v2;
}

inline auto adopt_val(wasm_val_t v) -> wasm::Val {
  switch (reveal_valkind(v.kind)) {
    case wasm::ValKind::I32:
      return wasm::Val(v.of.i32);
    case wasm::ValKind::I64:
      return wasm::Val(v.of.i64);
    case wasm::ValKind::F32:
      return wasm::Val(v.of.f32);
    case wasm::ValKind::F64:
      return wasm::Val(v.of.f64);
    case wasm::ValKind::EXTERNREF:
    case wasm::ValKind::FUNCREF:
      return wasm::Val(adopt_ref(v.of.ref));
    default:
      UNREACHABLE();
  }
}

struct borrowed_val {
  wasm::Val it;
  explicit borrowed_val(wasm::Val&& v) : it(std::move(v)) {}
  borrowed_val(borrowed_val&& that) : it(std::move(that.it)) {}
  ~borrowed_val() {
    if (it.is_ref()) it.release_ref().release();
  }
};

inline auto borrow_val(const wasm_val_t* v) -> borrowed_val {
  wasm::Val v2;
  switch (reveal_valkind(v->kind)) {
    case wasm::ValKind::I32:
      v2 = wasm::Val(v->of.i32);
      break;
    case wasm::ValKind::I64:
      v2 = wasm::Val(v->of.i64);
      break;
    case wasm::ValKind::F32:
      v2 = wasm::Val(v->of.f32);
      break;
    case wasm::ValKind::F64:
      v2 = wasm::Val(v->of.f64);
      break;
    case wasm::ValKind::EXTERNREF:
    case wasm::ValKind::FUNCREF:
      v2 = wasm::Val(adopt_ref(v->of.ref));
      break;
    default:
      UNREACHABLE();
  }
  return borrowed_val(std::move(v2));
}

}  // extern "C++"

WASM_DEFINE_VEC_BASE(val, wasm::Val, wasm::vec, )

void wasm_val_vec_new(wasm_val_vec_t* out, size_t size,
                      wasm_val_t const data[]) {
  auto v2 = wasm::vec<wasm::Val>::make_uninitialized(size);
  for (size_t i = 0; i < v2.size(); ++i) {
    v2[i] = adopt_val(data[i]);
  }
  *out = release_val_vec(std::move(v2));
}

void wasm_val_vec_copy(wasm_val_vec_t* out, const wasm_val_vec_t* v) {
  auto v2 = wasm::vec<wasm::Val>::make_uninitialized(v->size);
  for (size_t i = 0; i < v2.size(); ++i) {
    wasm_val_t val;
    wasm_val_copy(&v->data[i], &val);
    v2[i] = adopt_val(val);
  }
  *out = release_val_vec(std::move(v2));
}

void wasm_val_delete(wasm_val_t* v) {
  if (is_ref(reveal_valkind(v->kind))) {
    adopt_ref(v->of.ref);
  }
}

void wasm_val_copy(wasm_val_t* out, const wasm_val_t* v) {
  *out = *v;
  if (is_ref(reveal_valkind(v->kind))) {
    out->of.ref = v->of.ref ? release_ref(v->of.ref->copy()) : nullptr;
  }
}

///////////////////////////////////////////////////////////////////////////////
// Runtime Objects

// Frames

WASM_DEFINE_OWN(frame, wasm::Frame)
WASM_DEFINE_VEC_OWN(frame, wasm::Frame)

wasm_frame_t* wasm_frame_copy(const wasm_frame_t* frame) {
  return release_frame(frame->copy());
}

wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame);
// Defined below along with wasm_instance_t.

uint32_t wasm_frame_func_index(const wasm_frame_t* frame) {
  return reveal_frame(frame)->func_index();
}

size_t wasm_frame_func_offset(const wasm_frame_t* frame) {
  return reveal_frame(frame)->func_offset();
}

size_t wasm_frame_module_offset(const wasm_frame_t* frame) {
  return reveal_frame(frame)->module_offset();
}

// Traps

WASM_DEFINE_REF(trap, wasm::Trap)

wasm_trap_t* wasm_trap_new(wasm_store_t* store, const wasm_message_t* message) {
  auto message_ = borrow_byte_vec(message);
  return release_trap(wasm::Trap::make(store, message_.it));
}

void wasm_trap_message(const wasm_trap_t* trap, wasm_message_t* out) {
  *out = release_byte_vec(reveal_trap(trap)->message());
}

wasm_frame_t* wasm_trap_origin(const wasm_trap_t* trap) {
  return release_frame(reveal_trap(trap)->origin());
}

void wasm_trap_trace(const wasm_trap_t* trap, wasm_frame_vec_t* out) {
  *out = release_frame_vec(reveal_trap(trap)->trace());
}

// Foreign Objects

WASM_DEFINE_REF(foreign, wasm::Foreign)

wasm_foreign_t* wasm_foreign_new(wasm_store_t* store) {
  return release_foreign(wasm::Foreign::make(store));
}

// Modules

WASM_DEFINE_SHARABLE_REF(module, wasm::Module)

bool wasm_module_validate(wasm_store_t* store, const wasm_byte_vec_t* binary) {
  auto binary_ = borrow_byte_vec(binary);
  return wasm::Module::validate(store, binary_.it);
}

wasm_module_t* wasm_module_new(wasm_store_t* store,
                               const wasm_byte_vec_t* binary) {
  auto binary_ = borrow_byte_vec(binary);
  return release_module(wasm::Module::make(store, binary_.it));
}

void wasm_module_imports(const wasm_module_t* module,
                         wasm_importtype_vec_t* out) {
  *out = release_importtype_vec(reveal_module(module)->imports());
}

void wasm_module_exports(const wasm_module_t* module,
                         wasm_exporttype_vec_t* out) {
  *out = release_exporttype_vec(reveal_module(module)->exports());
}

void wasm_module_serialize(const wasm_module_t* module, wasm_byte_vec_t* out) {
  *out = release_byte_vec(reveal_module(module)->serialize());
}

wasm_module_t* wasm_module_deserialize(wasm_store_t* store,
                                       const wasm_byte_vec_t* binary) {
  auto binary_ = borrow_byte_vec(binary);
  return release_module(wasm::Module::deserialize(store, binary_.it));
}

wasm_shared_module_t* wasm_module_share(const wasm_module_t* module) {
  return release_shared_module(reveal_module(module)->share());
}

wasm_module_t* wasm_module_obtain(wasm_store_t* store,
                                  const wasm_shared_module_t* shared) {
  return release_module(wasm::Module::obtain(store, shared));
}

// Function Instances

WASM_DEFINE_REF(func, wasm::Func)

extern "C++" {

auto wasm_callback(void* env, const wasm::vec<wasm::Val>& args,
                   wasm::vec<wasm::Val>& results) -> wasm::own<wasm::Trap> {
  auto f = reinterpret_cast<wasm_func_callback_t>(env);
  return adopt_trap(f(hide_val_vec(args), hide_val_vec(results)));
}

struct wasm_callback_env_t {
  wasm_func_callback_with_env_t callback;
  void* env;
  void (*finalizer)(void*);
};

auto wasm_callback_with_env(void* env, const wasm::vec<wasm::Val>& args,
                            wasm::vec<wasm::Val>& results)
    -> wasm::own<wasm::Trap> {
  auto t = static_cast<wasm_callback_env_t*>(env);
  return adopt_trap(
      t->callback(t->env, hide_val_vec(args), hide_val_vec(results)));
}

void wasm_callback_env_finalizer(void* env) {
  auto t = static_cast<wasm_callback_env_t*>(env);
  if (t->finalizer) t->finalizer(t->env);
  delete t;
}

}  // extern "C++"

wasm_func_t* wasm_func_new(wasm_store_t* store, const wasm_functype_t* type,
                           wasm_func_callback_t callback) {
  return release_func(wasm::Func::make(store, type, wasm_callback,
                                       reinterpret_cast<void*>(callback)));
}

wasm_func_t* wasm_func_new_with_env(wasm_store_t* store,
                                    const wasm_functype_t* type,
                                    wasm_func_callback_with_env_t callback,
                                    void* env, void (*finalizer)(void*)) {
  auto env2 = new wasm_callback_env_t{callback, env, finalizer};
  return release_func(wasm::Func::make(store, type, wasm_callback_with_env,
                                       env2, wasm_callback_env_finalizer));
}

wasm_functype_t* wasm_func_type(const wasm_func_t* func) {
  return release_functype(func->type());
}

size_t wasm_func_param_arity(const wasm_func_t* func) {
  return func->param_arity();
}

size_t wasm_func_result_arity(const wasm_func_t* func) {
  return func->result_arity();
}

WASM_API_EXTERN wasm_trap_t* wasm_func_call(const wasm_func_t* func,
                                            const wasm_val_vec_t* args,
                                            wasm_val_vec_t* results) {
  auto v8_results = adopt_val_vec(results);
  auto ret = release_trap(func->call(adopt_val_vec(args), v8_results));
  *results = release_val_vec(std::move(v8_results));

  return ret;
}

// Global Instances

WASM_DEFINE_REF(global, wasm::Global)

wasm_global_t* wasm_global_new(wasm_store_t* store,
                               const wasm_globaltype_t* type,
                               const wasm_val_t* val) {
  auto val_ = borrow_val(val);
  return release_global(wasm::Global::make(store, type, val_.it));
}

wasm_globaltype_t* wasm_global_type(const wasm_global_t* global) {
  return release_globaltype(global->type());
}

void wasm_global_get(const wasm_global_t* global, wasm_val_t* out) {
  *out = release_val(global->get());
}

void wasm_global_set(wasm_global_t* global, const wasm_val_t* val) {
  auto val_ = borrow_val(val);
  global->set(val_.it);
}

// Table Instances

WASM_DEFINE_REF(table, wasm::Table)

wasm_table_t* wasm_table_new(wasm_store_t* store, const wasm_tabletype_t* type,
                             wasm_ref_t* ref) {
  return release_table(wasm::Table::make(store, type, ref));
}

wasm_tabletype_t* wasm_table_type(const wasm_table_t* table) {
  return release_tabletype(table->type());
}

wasm_ref_t* wasm_table_get(const wasm_table_t* table, wasm_table_size_t index) {
  return release_ref(table->get(index));
}

bool wasm_table_set(wasm_table_t* table, wasm_table_size_t index,
                    wasm_ref_t* ref) {
  return table->set(index, ref);
}

wasm_table_size_t wasm_table_size(const wasm_table_t* table) {
  return table->size();
}

bool wasm_table_grow(wasm_table_t* table, wasm_table_size_t delta,
                     wasm_ref_t* ref) {
  return table->grow(delta, ref);
}

// Memory Instances

WASM_DEFINE_REF(memory, wasm::Memory)

wasm_memory_t* wasm_memory_new(wasm_store_t* store,
                               const wasm_memorytype_t* type) {
  return release_memory(wasm::Memory::make(store, type));
}

wasm_memorytype_t* wasm_memory_type(const wasm_memory_t* memory) {
  return release_memorytype(memory->type());
}

wasm_byte_t* wasm_memory_data(wasm_memory_t* memory) { return memory->data(); }

size_t wasm_memory_data_size(const wasm_memory_t* memory) {
  return memory->data_size();
}

wasm_memory_pages_t wasm_memory_size(const wasm_memory_t* memory) {
  return memory->size();
}

bool wasm_memory_grow(wasm_memory_t* memory, wasm_memory_pages_t delta) {
  return memory->grow(delta);
}

// Externals

WASM_DEFINE_REF(extern, wasm::Extern)
WASM_DEFINE_VEC_OWN(extern, wasm::Extern)

wasm_externkind_t wasm_extern_kind(const wasm_extern_t* external) {
  return hide_externkind(external->kind());
}
wasm_externtype_t* wasm_extern_type(const wasm_extern_t* external) {
  return release_externtype(external->type());
}

wasm_extern_t* wasm_func_as_extern(wasm_func_t* func) {
  return hide_extern(static_cast<wasm::Extern*>(reveal_func(func)));
}
wasm_extern_t* wasm_global_as_extern(wasm_global_t* global) {
  return hide_extern(static_cast<wasm::Extern*>(reveal_global(global)));
}
wasm_extern_t* wasm_table_as_extern(wasm_table_t* table) {
  return hide_extern(static_cast<wasm::Extern*>(reveal_table(table)));
}
wasm_extern_t* wasm_memory_as_extern(wasm_memory_t* memory) {
  return hide_extern(static_cast<wasm::Extern*>(reveal_memory(memory)));
}

const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t* func) {
  return hide_extern(static_cast<const wasm::Extern*>(reveal_func(func)));
}
const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t* global) {
  return hide_extern(static_cast<const wasm::Extern*>(reveal_global(global)));
}
const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t* table) {
  return hide_extern(static_cast<const wasm::Extern*>(reveal_table(table)));
}
const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t* memory) {
  return hide_extern(static_cast<const wasm::Extern*>(reveal_memory(memory)));
}

wasm_func_t* wasm_extern_as_func(wasm_extern_t* external) {
  return hide_func(external->func());
}
wasm_global_t* wasm_extern_as_global(wasm_extern_t* external) {
  return hide_global(external->global());
}
wasm_table_t* wasm_extern_as_table(wasm_extern_t* external) {
  return hide_table(external->table());
}
wasm_memory_t* wasm_extern_as_memory(wasm_extern_t* external) {
  return hide_memory(external->memory());
}

const wasm_func_t* wasm_extern_as_func_const(const wasm_extern_t* external) {
  return hide_func(external->func());
}
const wasm_global_t* wasm_extern_as_global_const(
    const wasm_extern_t* external) {
  return hide_global(external->global());
}
const wasm_table_t* wasm_extern_as_table_const(const wasm_extern_t* external) {
  return hide_table(external->table());
}
const wasm_memory_t* wasm_extern_as_memory_const(
    const wasm_extern_t* external) {
  return hide_memory(external->memory());
}

// Module Instances

WASM_DEFINE_REF(instance, wasm::Instance)

WASM_API_EXTERN wasm_instance_t* wasm_instance_new(
    wasm_store_t* store, const wasm_module_t* module,
    const wasm_extern_vec_t* imports, wasm_trap_t** trap) {
  wasm::own<wasm::Trap> error;

  size_t size = 0;
  if (imports->data && imports->size > 0) {
    size = imports->size;
  }

  auto v8_imports = wasm::vec<wasm::Extern*>::make_uninitialized(size);

  for (size_t i = 0; i < v8_imports.size(); i++) {
    v8_imports[i] = reveal_extern(imports->data[i]);
  }

  wasm_instance_t* instance =
      release_instance(wasm::Instance::make(store, module, v8_imports, &error));
  if (trap) *trap = hide_trap(error.release());
  return instance;
}

void wasm_instance_exports(const wasm_instance_t* instance,
                           wasm_extern_vec_t* out) {
  *out = release_extern_vec(instance->exports());
}

wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame) {
  return hide_instance(reveal_frame(frame)->instance());
}

#undef WASM_DEFINE_OWN
#undef WASM_DEFINE_VEC_BASE
#undef WASM_DEFINE_VEC_PLAIN
#undef WASM_DEFINE_VEC_OWN
#undef WASM_DEFINE_TYPE
#undef WASM_DEFINE_REF_BASE
#undef WASM_DEFINE_REF
#undef WASM_DEFINE_SHARABLE_REF

}  // extern "C"