// 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 <cstring>
#include <iostream>

#include "third_party/wasm-api/wasm.h"
#include "third_party/wasm-api/wasm.hh"

#include "include/libplatform/libplatform.h"
#include "include/v8.h"
#include "src/api-inl.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-serialization.h"

// BEGIN FILE wasm-bin.cc

namespace wasm {
namespace bin {

////////////////////////////////////////////////////////////////////////////////
// Encoding

void encode_header(char*& ptr) {
  std::memcpy(ptr,
              "\x00"
              "asm\x01\x00\x00\x00",
              8);
  ptr += 8;
}

void encode_size32(char*& ptr, size_t n) {
  assert(n <= 0xffffffff);
  for (int i = 0; i < 5; ++i) {
    *ptr++ = (n & 0x7f) | (i == 4 ? 0x00 : 0x80);
    n = n >> 7;
  }
}

void encode_valtype(char*& ptr, const ValType* type) {
  switch (type->kind()) {
    case I32:
      *ptr++ = 0x7f;
      break;
    case I64:
      *ptr++ = 0x7e;
      break;
    case F32:
      *ptr++ = 0x7d;
      break;
    case F64:
      *ptr++ = 0x7c;
      break;
    case FUNCREF:
      *ptr++ = 0x70;
      break;
    case ANYREF:
      *ptr++ = 0x6f;
      break;
    default:
      UNREACHABLE();
  }
}

auto zero_size(const ValType* type) -> size_t {
  switch (type->kind()) {
    case I32:
      return 1;
    case I64:
      return 1;
    case F32:
      return 4;
    case F64:
      return 8;
    case FUNCREF:
      return 0;
    case ANYREF:
      return 0;
    default:
      UNREACHABLE();
  }
}

void encode_const_zero(char*& ptr, const ValType* type) {
  switch (type->kind()) {
    case I32:
      *ptr++ = 0x41;
      break;
    case I64:
      *ptr++ = 0x42;
      break;
    case F32:
      *ptr++ = 0x43;
      break;
    case F64:
      *ptr++ = 0x44;
      break;
    default:
      UNREACHABLE();
  }
  for (size_t i = 0; i < zero_size(type); ++i) *ptr++ = 0;
}

auto wrapper(const FuncType* type) -> vec<byte_t> {
  auto in_arity = type->params().size();
  auto out_arity = type->results().size();
  auto size = 39 + in_arity + out_arity;
  auto binary = vec<byte_t>::make_uninitialized(size);
  auto ptr = binary.get();

  encode_header(ptr);

  *ptr++ = i::wasm::kTypeSectionCode;
  encode_size32(ptr, 12 + in_arity + out_arity);  // size
  *ptr++ = 1;                                     // length
  *ptr++ = i::wasm::kWasmFunctionTypeCode;
  encode_size32(ptr, in_arity);
  for (size_t i = 0; i < in_arity; ++i) {
    encode_valtype(ptr, type->params()[i].get());
  }
  encode_size32(ptr, out_arity);
  for (size_t i = 0; i < out_arity; ++i) {
    encode_valtype(ptr, type->results()[i].get());
  }

  *ptr++ = i::wasm::kImportSectionCode;
  *ptr++ = 5;  // size
  *ptr++ = 1;  // length
  *ptr++ = 0;  // module length
  *ptr++ = 0;  // name length
  *ptr++ = i::wasm::kExternalFunction;
  *ptr++ = 0;  // type index

  *ptr++ = i::wasm::kExportSectionCode;
  *ptr++ = 4;  // size
  *ptr++ = 1;  // length
  *ptr++ = 0;  // name length
  *ptr++ = i::wasm::kExternalFunction;
  *ptr++ = 0;  // func index

  assert(ptr - binary.get() == static_cast<ptrdiff_t>(size));
  return binary;
}

auto wrapper(const GlobalType* type) -> vec<byte_t> {
  auto size = 25 + zero_size(type->content());
  auto binary = vec<byte_t>::make_uninitialized(size);
  auto ptr = binary.get();

  encode_header(ptr);

  *ptr++ = i::wasm::kGlobalSectionCode;
  encode_size32(ptr, 5 + zero_size(type->content()));  // size
  *ptr++ = 1;                                          // length
  encode_valtype(ptr, type->content());
  *ptr++ = (type->mutability() == VAR);
  encode_const_zero(ptr, type->content());
  *ptr++ = 0x0b;  // end

  *ptr++ = i::wasm::kExportSectionCode;
  *ptr++ = 4;  // size
  *ptr++ = 1;  // length
  *ptr++ = 0;  // name length
  *ptr++ = i::wasm::kExternalGlobal;
  *ptr++ = 0;  // func index

  assert(ptr - binary.get() == static_cast<ptrdiff_t>(size));
  return binary;
}

////////////////////////////////////////////////////////////////////////////////
// Decoding

// Numbers

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

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

void u32_skip(const byte_t*& pos) { bin::u32(pos); }

// Names

auto name(const byte_t*& pos) -> Name {
  auto size = bin::u32(pos);
  auto start = pos;
  auto name = Name::make_uninitialized(size);
  std::memcpy(name.get(), start, size);
  pos += size;
  return name;
}

void name_skip(const byte_t*& pos) {
  auto size = bin::u32(pos);
  pos += size;
}

// Types

auto valtype(const byte_t*& pos) -> own<wasm::ValType*> {
  switch (*pos++) {
    case i::wasm::kLocalI32:
      return ValType::make(I32);
    case i::wasm::kLocalI64:
      return ValType::make(I64);
    case i::wasm::kLocalF32:
      return ValType::make(F32);
    case i::wasm::kLocalF64:
      return ValType::make(F64);
    case i::wasm::kLocalAnyFunc:
      return ValType::make(FUNCREF);
    case i::wasm::kLocalAnyRef:
      return ValType::make(ANYREF);
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
  return {};
}

auto mutability(const byte_t*& pos) -> Mutability {
  return *pos++ ? VAR : CONST;
}

auto limits(const byte_t*& pos) -> Limits {
  auto tag = *pos++;
  auto min = bin::u32(pos);
  if ((tag & 0x01) == 0) {
    return Limits(min);
  } else {
    auto max = bin::u32(pos);
    return Limits(min, max);
  }
}

auto stacktype(const byte_t*& pos) -> vec<ValType*> {
  size_t size = bin::u32(pos);
  auto v = vec<ValType*>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; ++i) v[i] = bin::valtype(pos);
  return v;
}

auto functype(const byte_t*& pos) -> own<FuncType*> {
  assert(*pos == i::wasm::kWasmFunctionTypeCode);
  ++pos;
  auto params = bin::stacktype(pos);
  auto results = bin::stacktype(pos);
  return FuncType::make(std::move(params), std::move(results));
}

auto globaltype(const byte_t*& pos) -> own<GlobalType*> {
  auto content = bin::valtype(pos);
  auto mutability = bin::mutability(pos);
  return GlobalType::make(std::move(content), mutability);
}

auto tabletype(const byte_t*& pos) -> own<TableType*> {
  auto elem = bin::valtype(pos);
  auto limits = bin::limits(pos);
  return TableType::make(std::move(elem), limits);
}

auto memorytype(const byte_t*& pos) -> own<MemoryType*> {
  auto limits = bin::limits(pos);
  return MemoryType::make(limits);
}

// Expressions

void expr_skip(const byte_t*& pos) {
  switch (*pos++) {
    case i::wasm::kExprI32Const:
    case i::wasm::kExprI64Const:
    case i::wasm::kExprGetGlobal: {
      bin::u32_skip(pos);
    } break;
    case i::wasm::kExprF32Const: {
      pos += 4;
    } break;
    case i::wasm::kExprF64Const: {
      pos += 8;
    } break;
    default: {
      // TODO(wasm+): support new expression forms
      UNREACHABLE();
    }
  }
  ++pos;  // end
}

// Sections

auto section(const vec<byte_t>& binary, i::wasm::SectionCode sec)
    -> const byte_t* {
  const byte_t* end = binary.get() + binary.size();
  const byte_t* pos = binary.get() + 8;  // skip header
  while (pos < end && *pos++ != sec) {
    auto size = bin::u32(pos);
    pos += size;
  }
  if (pos == end) return nullptr;
  bin::u32_skip(pos);
  return pos;
}

// Only for asserts/DCHECKs.
auto section_end(const vec<byte_t>& binary, i::wasm::SectionCode sec)
    -> const byte_t* {
  const byte_t* end = binary.get() + binary.size();
  const byte_t* pos = binary.get() + 8;  // skip header
  while (pos < end && *pos != sec) {
    ++pos;
    auto size = bin::u32(pos);
    pos += size;
  }
  if (pos == end) return nullptr;
  ++pos;
  auto size = bin::u32(pos);
  return pos + size;
}

// Type section

auto types(const vec<byte_t>& binary) -> vec<FuncType*> {
  auto pos = bin::section(binary, i::wasm::kTypeSectionCode);
  if (pos == nullptr) return vec<FuncType*>::make();
  size_t size = bin::u32(pos);
  // TODO(wasm+): support new deftypes
  auto v = vec<FuncType*>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; ++i) {
    v[i] = bin::functype(pos);
  }
  assert(pos == bin::section_end(binary, i::wasm::kTypeSectionCode));
  return v;
}

// Import section

auto imports(const vec<byte_t>& binary, const vec<FuncType*>& types)
    -> vec<ImportType*> {
  auto pos = bin::section(binary, i::wasm::kImportSectionCode);
  if (pos == nullptr) return vec<ImportType*>::make();
  size_t size = bin::u32(pos);
  auto v = vec<ImportType*>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; ++i) {
    auto module = bin::name(pos);
    auto name = bin::name(pos);
    own<ExternType*> type;
    switch (*pos++) {
      case i::wasm::kExternalFunction:
        type = types[bin::u32(pos)]->copy();
        break;
      case i::wasm::kExternalTable:
        type = bin::tabletype(pos);
        break;
      case i::wasm::kExternalMemory:
        type = bin::memorytype(pos);
        break;
      case i::wasm::kExternalGlobal:
        type = bin::globaltype(pos);
        break;
      default:
        UNREACHABLE();
    }
    v[i] =
        ImportType::make(std::move(module), std::move(name), std::move(type));
  }
  assert(pos == bin::section_end(binary, i::wasm::kImportSectionCode));
  return v;
}

auto count(const vec<ImportType*>& imports, ExternKind kind) -> uint32_t {
  uint32_t n = 0;
  for (uint32_t i = 0; i < imports.size(); ++i) {
    if (imports[i]->type()->kind() == kind) ++n;
  }
  return n;
}

// Function section

auto funcs(const vec<byte_t>& binary, const vec<ImportType*>& imports,
           const vec<FuncType*>& types) -> vec<FuncType*> {
  auto pos = bin::section(binary, i::wasm::kFunctionSectionCode);
  size_t size = pos != nullptr ? bin::u32(pos) : 0;
  auto v =
      vec<FuncType*>::make_uninitialized(size + count(imports, EXTERN_FUNC));
  size_t j = 0;
  for (uint32_t i = 0; i < imports.size(); ++i) {
    auto et = imports[i]->type();
    if (et->kind() == EXTERN_FUNC) {
      v[j++] = et->func()->copy();
    }
  }
  if (pos != nullptr) {
    for (; j < v.size(); ++j) {
      v[j] = types[bin::u32(pos)]->copy();
    }
    assert(pos == bin::section_end(binary, i::wasm::kFunctionSectionCode));
  }
  return v;
}

// Global section

auto globals(const vec<byte_t>& binary, const vec<ImportType*>& imports)
    -> vec<GlobalType*> {
  auto pos = bin::section(binary, i::wasm::kGlobalSectionCode);
  size_t size = pos != nullptr ? bin::u32(pos) : 0;
  auto v = vec<GlobalType*>::make_uninitialized(size +
                                                count(imports, EXTERN_GLOBAL));
  size_t j = 0;
  for (uint32_t i = 0; i < imports.size(); ++i) {
    auto et = imports[i]->type();
    if (et->kind() == EXTERN_GLOBAL) {
      v[j++] = et->global()->copy();
    }
  }
  if (pos != nullptr) {
    for (; j < v.size(); ++j) {
      v[j] = bin::globaltype(pos);
      expr_skip(pos);
    }
    assert(pos == bin::section_end(binary, i::wasm::kGlobalSectionCode));
  }
  return v;
}

// Table section

auto tables(const vec<byte_t>& binary, const vec<ImportType*>& imports)
    -> vec<TableType*> {
  auto pos = bin::section(binary, i::wasm::kTableSectionCode);
  size_t size = pos != nullptr ? bin::u32(pos) : 0;
  auto v =
      vec<TableType*>::make_uninitialized(size + count(imports, EXTERN_TABLE));
  size_t j = 0;
  for (uint32_t i = 0; i < imports.size(); ++i) {
    auto et = imports[i]->type();
    if (et->kind() == EXTERN_TABLE) {
      v[j++] = et->table()->copy();
    }
  }
  if (pos != nullptr) {
    for (; j < v.size(); ++j) {
      v[j] = bin::tabletype(pos);
    }
    assert(pos == bin::section_end(binary, i::wasm::kTableSectionCode));
  }
  return v;
}

// Memory section

auto memories(const vec<byte_t>& binary, const vec<ImportType*>& imports)
    -> vec<MemoryType*> {
  auto pos = bin::section(binary, i::wasm::kMemorySectionCode);
  size_t size = pos != nullptr ? bin::u32(pos) : 0;
  auto v = vec<MemoryType*>::make_uninitialized(size +
                                                count(imports, EXTERN_MEMORY));
  size_t j = 0;
  for (uint32_t i = 0; i < imports.size(); ++i) {
    auto et = imports[i]->type();
    if (et->kind() == EXTERN_MEMORY) {
      v[j++] = et->memory()->copy();
    }
  }
  if (pos != nullptr) {
    for (; j < v.size(); ++j) {
      v[j] = bin::memorytype(pos);
    }
    assert(pos == bin::section_end(binary, i::wasm::kMemorySectionCode));
  }
  return v;
}

// Export section

auto exports(const vec<byte_t>& binary, const vec<FuncType*>& funcs,
             const vec<GlobalType*>& globals, const vec<TableType*>& tables,
             const vec<MemoryType*>& memories) -> vec<ExportType*> {
  auto pos = bin::section(binary, i::wasm::kExportSectionCode);
  if (pos == nullptr) return vec<ExportType*>::make();
  size_t size = bin::u32(pos);
  auto exports = vec<ExportType*>::make_uninitialized(size);
  for (uint32_t i = 0; i < size; ++i) {
    auto name = bin::name(pos);
    auto tag = *pos++;
    auto index = bin::u32(pos);
    own<ExternType*> type;
    switch (tag) {
      case i::wasm::kExternalFunction:
        type = funcs[index]->copy();
        break;
      case i::wasm::kExternalTable:
        type = tables[index]->copy();
        break;
      case i::wasm::kExternalMemory:
        type = memories[index]->copy();
        break;
      case i::wasm::kExternalGlobal:
        type = globals[index]->copy();
        break;
      default:
        UNREACHABLE();
    }
    exports[i] = ExportType::make(std::move(name), std::move(type));
  }
  assert(pos == bin::section_end(binary, i::wasm::kExportSectionCode));
  return exports;
}

auto imports(const vec<byte_t>& binary) -> vec<ImportType*> {
  return bin::imports(binary, bin::types(binary));
}

auto exports(const vec<byte_t>& binary) -> vec<ExportType*> {
  auto types = bin::types(binary);
  auto imports = bin::imports(binary, types);
  auto funcs = bin::funcs(binary, imports, types);
  auto globals = bin::globals(binary, imports);
  auto tables = bin::tables(binary, imports);
  auto memories = bin::memories(binary, imports);
  return bin::exports(binary, funcs, globals, tables, memories);
}

}  // namespace bin
}  // namespace wasm

// BEGIN FILE wasm-v8-lowlevel.cc

namespace v8 {
namespace wasm {

// Objects

auto object_isolate(const v8::Persistent<v8::Object>& obj) -> v8::Isolate* {
  struct FakePersistent {
    v8::Object* val;
  };
  auto v8_obj = reinterpret_cast<const FakePersistent*>(&obj)->val;
  return v8_obj->GetIsolate();
}

template <class T>
auto object_handle(T v8_obj) -> i::Handle<T> {
  return handle(v8_obj, v8_obj->GetIsolate());
}

// Foreign pointers

auto foreign_new(v8::Isolate* isolate, void* ptr) -> v8::Local<v8::Value> {
  auto foreign = v8::FromCData(reinterpret_cast<i::Isolate*>(isolate),
                               reinterpret_cast<i::Address>(ptr));
  return v8::Utils::ToLocal(foreign);
}

auto foreign_get(v8::Local<v8::Value> val) -> void* {
  auto foreign = v8::Utils::OpenHandle(*val);
  if (!foreign->IsForeign()) return nullptr;
  auto addr = v8::ToCData<i::Address>(*foreign);
  return reinterpret_cast<void*>(addr);
}

// Types

auto v8_valtype_to_wasm(i::wasm::ValueType v8_valtype) -> ::wasm::ValKind {
  switch (v8_valtype) {
    case i::wasm::kWasmI32:
      return ::wasm::I32;
    case i::wasm::kWasmI64:
      return ::wasm::I64;
    case i::wasm::kWasmF32:
      return ::wasm::F32;
    case i::wasm::kWasmF64:
      return ::wasm::F64;
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
}

auto func_type_param_arity(v8::Local<v8::Object> function) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(function);
  auto v8_function = i::Handle<i::WasmExportedFunction>::cast(v8_object);
  i::wasm::FunctionSig* sig = v8_function->instance()
                                  ->module()
                                  ->functions[v8_function->function_index()]
                                  .sig;
  return static_cast<uint32_t>(sig->parameter_count());
}

auto func_type_result_arity(v8::Local<v8::Object> function) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(function);
  auto v8_function = i::Handle<i::WasmExportedFunction>::cast(v8_object);
  i::wasm::FunctionSig* sig = v8_function->instance()
                                  ->module()
                                  ->functions[v8_function->function_index()]
                                  .sig;
  return static_cast<uint32_t>(sig->return_count());
}

auto func_type_param(v8::Local<v8::Object> function, size_t i)
    -> ::wasm::ValKind {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(function);
  auto v8_function = i::Handle<i::WasmExportedFunction>::cast(v8_object);
  i::wasm::FunctionSig* sig = v8_function->instance()
                                  ->module()
                                  ->functions[v8_function->function_index()]
                                  .sig;
  return v8_valtype_to_wasm(sig->GetParam(i));
}

auto func_type_result(v8::Local<v8::Object> function, size_t i)
    -> ::wasm::ValKind {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(function);
  auto v8_function = i::Handle<i::WasmExportedFunction>::cast(v8_object);
  i::wasm::FunctionSig* sig = v8_function->instance()
                                  ->module()
                                  ->functions[v8_function->function_index()]
                                  .sig;
  return v8_valtype_to_wasm(sig->GetReturn(i));
}

auto global_type_content(v8::Local<v8::Object> global) -> ::wasm::ValKind {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_valtype_to_wasm(v8_global->type());
}

auto global_type_mutable(v8::Local<v8::Object> global) -> bool {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_global->is_mutable();
}

auto table_type_min(v8::Local<v8::Object> table) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  return v8_table->current_length();
}

auto table_type_max(v8::Local<v8::Object> table) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  auto v8_max_obj = v8_table->maximum_length();
  uint32_t max;
  return v8_max_obj->ToUint32(&max) ? max : 0xffffffffu;
}

auto memory_size(v8::Local<v8::Object> memory) -> uint32_t;

auto memory_type_min(v8::Local<v8::Object> memory) -> uint32_t {
  return memory_size(memory);
}

auto memory_type_max(v8::Local<v8::Object> memory) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(memory);
  auto v8_memory = i::Handle<i::WasmMemoryObject>::cast(v8_object);
  return v8_memory->has_maximum_pages() ? v8_memory->maximum_pages()
                                        : 0xffffffffu;
}

// Modules

auto module_binary_size(v8::Local<v8::Object> module) -> size_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(module);
  auto v8_module = i::Handle<i::WasmModuleObject>::cast(v8_object);
  return v8_module->native_module()->wire_bytes().size();
}

auto module_binary(v8::Local<v8::Object> module) -> const char* {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(module);
  auto v8_module = i::Handle<i::WasmModuleObject>::cast(v8_object);
  return reinterpret_cast<const char*>(
      v8_module->native_module()->wire_bytes().start());
}

auto module_serialize_size(v8::Local<v8::Object> module) -> size_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(module);
  auto v8_module = i::Handle<i::WasmModuleObject>::cast(v8_object);
  i::wasm::WasmSerializer serializer(v8_module->native_module());
  return serializer.GetSerializedNativeModuleSize();
}

auto module_serialize(v8::Local<v8::Object> module, char* buffer, size_t size)
    -> bool {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(module);
  auto v8_module = i::Handle<i::WasmModuleObject>::cast(v8_object);
  i::wasm::WasmSerializer serializer(v8_module->native_module());
  return serializer.SerializeNativeModule(
      {reinterpret_cast<uint8_t*>(buffer), size});
}

auto module_deserialize(v8::Isolate* isolate, const char* binary,
                        size_t binary_size, const char* buffer,
                        size_t buffer_size) -> v8::MaybeLocal<v8::Object> {
  auto v8_isolate = reinterpret_cast<i::Isolate*>(isolate);
  auto maybe_v8_module = i::wasm::DeserializeNativeModule(
      v8_isolate, {reinterpret_cast<const uint8_t*>(buffer), buffer_size},
      {reinterpret_cast<const uint8_t*>(binary), binary_size});
  if (maybe_v8_module.is_null()) return v8::MaybeLocal<v8::Object>();
  auto v8_module =
      i::Handle<i::JSObject>::cast(maybe_v8_module.ToHandleChecked());
  return v8::MaybeLocal<v8::Object>(v8::Utils::ToLocal(v8_module));
}

// Instances

auto instance_module(v8::Local<v8::Object> instance) -> v8::Local<v8::Object> {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(instance);
  auto v8_instance = i::Handle<i::WasmInstanceObject>::cast(v8_object);
  auto v8_module =
      object_handle(i::JSObject::cast(v8_instance->module_object()));
  return v8::Utils::ToLocal(v8_module);
}

auto instance_exports(v8::Local<v8::Object> instance) -> v8::Local<v8::Object> {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(instance);
  auto v8_instance = i::Handle<i::WasmInstanceObject>::cast(v8_object);
  auto v8_exports = object_handle(v8_instance->exports_object());
  return v8::Utils::ToLocal(v8_exports);
}

// Externals

auto extern_kind(v8::Local<v8::Object> external) -> ::wasm::ExternKind {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(external);

  if (i::WasmExportedFunction::IsWasmExportedFunction(*v8_object))
    return ::wasm::EXTERN_FUNC;
  if (v8_object->IsWasmGlobalObject()) return ::wasm::EXTERN_GLOBAL;
  if (v8_object->IsWasmTableObject()) return ::wasm::EXTERN_TABLE;
  if (v8_object->IsWasmMemoryObject()) return ::wasm::EXTERN_MEMORY;
  UNREACHABLE();
}

// Functions

auto func_instance(v8::Local<v8::Function> function) -> v8::Local<v8::Object> {
  auto v8_function = v8::Utils::OpenHandle(*function);
  auto v8_func = i::Handle<i::WasmExportedFunction>::cast(v8_function);
  auto v8_instance = object_handle(i::JSObject::cast(v8_func->instance()));
  return v8::Utils::ToLocal(v8_instance);
}

// Globals

auto global_get_i32(v8::Local<v8::Object> global) -> int32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_global->GetI32();
}
auto global_get_i64(v8::Local<v8::Object> global) -> int64_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_global->GetI64();
}
auto global_get_f32(v8::Local<v8::Object> global) -> float {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_global->GetF32();
}
auto global_get_f64(v8::Local<v8::Object> global) -> double {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  return v8_global->GetF64();
}

void global_set_i32(v8::Local<v8::Object> global, int32_t val) {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  v8_global->SetI32(val);
}
void global_set_i64(v8::Local<v8::Object> global, int64_t val) {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  v8_global->SetI64(val);
}
void global_set_f32(v8::Local<v8::Object> global, float val) {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  v8_global->SetF32(val);
}
void global_set_f64(v8::Local<v8::Object> global, double val) {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(global);
  auto v8_global = i::Handle<i::WasmGlobalObject>::cast(v8_object);
  v8_global->SetF32(val);
}

// Tables

auto table_get(v8::Local<v8::Object> table, size_t index)
    -> v8::MaybeLocal<v8::Function> {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  if (index > std::numeric_limits<int>::max()) return {};
  i::Isolate* isolate = v8_table->GetIsolate();
  i::MaybeHandle<i::Object> maybe_result =
      i::WasmTableObject::Get(isolate, v8_table, static_cast<int>(index));
  i::Handle<i::Object> result;
  if (!maybe_result.ToHandle(&result)) {
    // TODO(jkummerow): Clear pending exception?
    return {};
  }
  if (!result->IsJSFunction()) return {};
  return v8::MaybeLocal<v8::Function>(
      v8::Utils::ToLocal(i::Handle<i::JSFunction>::cast(result)));
}

auto table_set(v8::Local<v8::Object> table, size_t index,
               v8::MaybeLocal<v8::Function> maybe) -> bool {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  i::Handle<i::Object> v8_function =
      maybe.IsEmpty()
          ? i::Handle<i::Object>::cast(
                i::ReadOnlyRoots(v8_table->GetIsolate()).null_value_handle())
          : i::Handle<i::Object>::cast(
                v8::Utils::OpenHandle<v8::Function, i::JSReceiver>(
                    maybe.ToLocalChecked()));
  if (index >= v8_table->current_length()) return false;

  {
    v8::TryCatch handler(table->GetIsolate());
    i::WasmTableObject::Set(v8_table->GetIsolate(), v8_table,
                            static_cast<uint32_t>(index), v8_function);
    if (handler.HasCaught()) return false;
  }

  return true;
}

auto table_size(v8::Local<v8::Object> table) -> size_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  return v8_table->current_length();
}

auto table_grow(v8::Local<v8::Object> table, size_t delta,
                v8::MaybeLocal<v8::Function> init) -> bool {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(table);
  auto v8_table = i::Handle<i::WasmTableObject>::cast(v8_object);
  if (delta > 0xfffffffflu) return false;
  auto old_size = v8_table->current_length();
  // TODO(jkummerow): Overflow check.
  auto new_size = old_size + static_cast<uint32_t>(delta);
  // TODO(v8): This should happen in WasmTableObject::Grow.
  if (new_size > table_type_max(table)) return false;

  {
    v8::TryCatch handler(table->GetIsolate());
    v8_table->Grow(v8_table->GetIsolate(), static_cast<uint32_t>(delta));
    if (handler.HasCaught()) return false;
  }

  // TODO(v8): This should happen in WasmTableObject::Grow.
  if (new_size != old_size) {
    auto isolate = v8_table->GetIsolate();
    i::Handle<i::FixedArray> old_array(v8_table->elements(), isolate);
    auto new_array =
        isolate->factory()->NewFixedArray(static_cast<int>(new_size));
    assert(static_cast<uint32_t>(old_array->length()) == old_size);
    for (int i = 0; i < static_cast<int>(old_size); ++i)
      new_array->set(i, old_array->get(i));
    i::Handle<i::Object> val = isolate->factory()->null_value();
    if (!init.IsEmpty())
      val = v8::Utils::OpenHandle<v8::Function, i::JSReceiver>(
          init.ToLocalChecked());
    for (int i = old_size; i < static_cast<int>(new_size); ++i)
      new_array->set(i, *val);
    v8_table->set_elements(*new_array);
  }

  return true;
}

// Memory

auto memory_data(v8::Local<v8::Object> memory) -> char* {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(memory);
  auto v8_memory = i::Handle<i::WasmMemoryObject>::cast(v8_object);
  return reinterpret_cast<char*>(v8_memory->array_buffer()->backing_store());
}

auto memory_data_size(v8::Local<v8::Object> memory) -> size_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(memory);
  auto v8_memory = i::Handle<i::WasmMemoryObject>::cast(v8_object);
  return v8_memory->array_buffer()->byte_length();
}

auto memory_size(v8::Local<v8::Object> memory) -> uint32_t {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(memory);
  auto v8_memory = i::Handle<i::WasmMemoryObject>::cast(v8_object);
  return static_cast<uint32_t>(v8_memory->array_buffer()->byte_length() /
                               i::wasm::kWasmPageSize);
}

auto memory_grow(v8::Local<v8::Object> memory, uint32_t delta) -> bool {
  auto v8_object = v8::Utils::OpenHandle<v8::Object, i::JSReceiver>(memory);
  auto v8_memory = i::Handle<i::WasmMemoryObject>::cast(v8_object);
  auto old =
      i::WasmMemoryObject::Grow(v8_memory->GetIsolate(), v8_memory, delta);
  return old != -1;
}

}  // namespace wasm
}  // namespace v8

/// BEGIN FILE wasm-v8.cc

namespace wasm {

///////////////////////////////////////////////////////////////////////////////
// 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);
}

#ifdef DEBUG
template <class T>
void vec<T>::make_data() {}

template <class T>
void vec<T>::free_data() {}
#endif

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

// Configuration

struct ConfigImpl {
  ConfigImpl() {}
  ~ConfigImpl() {}
};

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

Config::~Config() { impl(this)->~ConfigImpl(); }

void Config::operator delete(void* p) { ::operator delete(p); }

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

// Engine

struct EngineImpl {
  static bool created;

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

  EngineImpl() {
    assert(!created);
    created = true;
  }

  ~EngineImpl() {
    v8::V8::Dispose();
    v8::V8::ShutdownPlatform();
  }
};

bool EngineImpl::created = false;

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

Engine::~Engine() { impl(this)->~EngineImpl(); }

void Engine::operator delete(void* p) { ::operator delete(p); }

auto Engine::make(own<Config*>&& config) -> own<Engine*> {
  i::FLAG_expose_gc = true;
  i::FLAG_experimental_wasm_bigint = true;
  i::FLAG_experimental_wasm_mv = true;
  auto engine = new (std::nothrow) EngineImpl;
  if (!engine) return own<Engine*>();
  engine->platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(engine->platform.get());
  v8::V8::Initialize();
  return make_own(seal<Engine>(engine));
}

// Stores

enum v8_string_t {
  V8_S_EMPTY,
  V8_S_I32,
  V8_S_I64,
  V8_S_F32,
  V8_S_F64,
  V8_S_ANYREF,
  V8_S_ANYFUNC,
  V8_S_VALUE,
  V8_S_MUTABLE,
  V8_S_ELEMENT,
  V8_S_MINIMUM,
  V8_S_MAXIMUM,
  V8_S_COUNT
};

enum v8_symbol_t { V8_Y_CALLBACK, V8_Y_ENV, V8_Y_COUNT };

enum v8_function_t {
  V8_F_WEAKMAP,
  V8_F_WEAKMAP_PROTO,
  V8_F_WEAKMAP_GET,
  V8_F_WEAKMAP_SET,
  V8_F_MODULE,
  V8_F_GLOBAL,
  V8_F_TABLE,
  V8_F_MEMORY,
  V8_F_INSTANCE,
  V8_F_VALIDATE,
  V8_F_COUNT,
};

class StoreImpl {
  friend own<Store*> Store::make(Engine*);

  v8::Isolate::CreateParams create_params_;
  v8::Isolate* isolate_;
  v8::Eternal<v8::Context> context_;
  v8::Eternal<v8::String> strings_[V8_S_COUNT];
  v8::Eternal<v8::Symbol> symbols_[V8_Y_COUNT];
  v8::Eternal<v8::Function> functions_[V8_F_COUNT];
  v8::Eternal<v8::Object> host_data_map_;
  v8::Eternal<v8::Symbol> callback_symbol_;

 public:
  StoreImpl() {}

  ~StoreImpl() {
#ifdef DEBUG
    reinterpret_cast<i::Isolate*>(isolate_)->heap()->PreciseCollectAllGarbage(
        i::Heap::kNoGCFlags, i::GarbageCollectionReason::kTesting,
        v8::kGCCallbackFlagForced);
#endif
    context()->Exit();
    isolate_->Exit();
    isolate_->Dispose();
    delete create_params_.array_buffer_allocator;
  }

  auto isolate() const -> v8::Isolate* { return isolate_; }

  auto context() const -> v8::Local<v8::Context> {
    return context_.Get(isolate_);
  }

  auto v8_string(v8_string_t i) const -> v8::Local<v8::String> {
    return strings_[i].Get(isolate_);
  }
  auto v8_string(v8_symbol_t i) const -> v8::Local<v8::Symbol> {
    return symbols_[i].Get(isolate_);
  }
  auto v8_function(v8_function_t i) const -> v8::Local<v8::Function> {
    return functions_[i].Get(isolate_);
  }

  auto host_data_map() const -> v8::Local<v8::Object> {
    return host_data_map_.Get(isolate_);
  }

  static auto get(v8::Isolate* isolate) -> StoreImpl* {
    return static_cast<StoreImpl*>(isolate->GetData(0));
  }
};

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

Store::~Store() { impl(this)->~StoreImpl(); }

void Store::operator delete(void* p) { ::operator delete(p); }

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();
  auto isolate = v8::Isolate::New(store->create_params_);
  if (!isolate) return own<Store*>();

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

    // Create context.
    auto context = v8::Context::New(isolate);
    if (context.IsEmpty()) return own<Store*>();
    v8::Context::Scope context_scope(context);

    store->isolate_ = isolate;
    store->context_ = v8::Eternal<v8::Context>(isolate, context);

    // Create strings.
    static const char* const raw_strings[V8_S_COUNT] = {
        "",        "i32",   "i64",     "f32",     "f64",     "anyref",
        "anyfunc", "value", "mutable", "element", "initial", "maximum",
    };
    for (int i = 0; i < V8_S_COUNT; ++i) {
      auto maybe = v8::String::NewFromUtf8(isolate, raw_strings[i],
                                           v8::NewStringType::kNormal);
      if (maybe.IsEmpty()) return own<Store*>();
      auto string = maybe.ToLocalChecked();
      store->strings_[i] = v8::Eternal<v8::String>(isolate, string);
    }

    for (int i = 0; i < V8_Y_COUNT; ++i) {
      auto symbol = v8::Symbol::New(isolate);
      store->symbols_[i] = v8::Eternal<v8::Symbol>(isolate, symbol);
    }

    // Extract functions.
    auto global = context->Global();
    auto maybe_wasm_name = v8::String::NewFromUtf8(isolate, "WebAssembly",
                                                   v8::NewStringType::kNormal);
    if (maybe_wasm_name.IsEmpty()) return own<Store*>();
    auto wasm_name = maybe_wasm_name.ToLocalChecked();
    auto maybe_wasm = global->Get(context, wasm_name);
    if (maybe_wasm.IsEmpty()) return own<Store*>();
    auto wasm = v8::Local<v8::Object>::Cast(maybe_wasm.ToLocalChecked());
    v8::Local<v8::Object> weakmap;
    v8::Local<v8::Object> weakmap_proto;

    struct {
      const char* name;
      v8::Local<v8::Object>* carrier;
    } raw_functions[V8_F_COUNT] = {
        {"WeakMap", &global},    {"prototype", &weakmap},
        {"get", &weakmap_proto}, {"set", &weakmap_proto},
        {"Module", &wasm},       {"Global", &wasm},
        {"Table", &wasm},        {"Memory", &wasm},
        {"Instance", &wasm},     {"validate", &wasm},
    };
    for (int i = 0; i < V8_F_COUNT; ++i) {
      auto maybe_name = v8::String::NewFromUtf8(isolate, raw_functions[i].name,
                                                v8::NewStringType::kNormal);
      if (maybe_name.IsEmpty()) return own<Store*>();
      auto name = maybe_name.ToLocalChecked();
      assert(!raw_functions[i].carrier->IsEmpty());
      // TODO(wasm+): remove
      if ((*raw_functions[i].carrier)->IsUndefined()) continue;
      auto maybe_obj = (*raw_functions[i].carrier)->Get(context, name);
      if (maybe_obj.IsEmpty()) return own<Store*>();
      auto obj = v8::Local<v8::Object>::Cast(maybe_obj.ToLocalChecked());
      if (i == V8_F_WEAKMAP_PROTO) {
        assert(obj->IsObject());
        weakmap_proto = obj;
      } else {
        assert(obj->IsFunction());
        auto function = v8::Local<v8::Function>::Cast(obj);
        store->functions_[i] = v8::Eternal<v8::Function>(isolate, function);
        if (i == V8_F_WEAKMAP) weakmap = function;
      }
    }

    // Create host data weak map.
    v8::Local<v8::Value>* empty_args = nullptr;
    auto maybe_weakmap =
        store->v8_function(V8_F_WEAKMAP)->NewInstance(context, 0, empty_args);
    if (maybe_weakmap.IsEmpty()) return own<Store*>();
    auto map = v8::Local<v8::Object>::Cast(maybe_weakmap.ToLocalChecked());
    assert(map->IsWeakMap());
    store->host_data_map_ = v8::Eternal<v8::Object>(isolate, map);
  }

  store->isolate()->Enter();
  store->context()->Enter();
  isolate->SetData(0, store.get());

  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* valtypes[] = {
    new ValTypeImpl(I32), new ValTypeImpl(I64),    new ValTypeImpl(F32),
    new ValTypeImpl(F64), new ValTypeImpl(ANYREF), new ValTypeImpl(FUNCREF),
};

ValType::~ValType() {}

void ValType::operator delete(void*) {}

auto ValType::make(ValKind k) -> own<ValType*> {
  auto result = seal<ValType>(valtypes[k]);
  return own<ValType*>(result);
}

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

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

// Extern Types

struct ExternTypeImpl {
  ExternKind kind;

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

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

ExternType::~ExternType() { impl(this)->~ExternTypeImpl(); }

void ExternType::operator delete(void* p) { ::operator delete(p); }

auto ExternType::copy() const -> own<ExternType*> {
  switch (kind()) {
    case EXTERN_FUNC:
      return func()->copy();
    case EXTERN_GLOBAL:
      return global()->copy();
    case EXTERN_TABLE:
      return table()->copy();
    case EXTERN_MEMORY:
      return memory()->copy();
  }
}

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

// Function Types

struct FuncTypeImpl : ExternTypeImpl {
  vec<ValType*> params;
  vec<ValType*> results;

  FuncTypeImpl(vec<ValType*>& params, vec<ValType*>& results)
      : ExternTypeImpl(EXTERN_FUNC),
        params(std::move(params)),
        results(std::move(results)) {}

  ~FuncTypeImpl() {}
};

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

FuncType::~FuncType() {}

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

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

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

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

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

auto ExternType::func() const -> const FuncType* {
  return kind() == EXTERN_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(EXTERN_GLOBAL),
        content(std::move(content)),
        mutability(mutability) {}

  ~GlobalTypeImpl() {}
};

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

GlobalType::~GlobalType() {}

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() == EXTERN_GLOBAL
             ? seal<GlobalType>(static_cast<GlobalTypeImpl*>(impl(this)))
             : nullptr;
}

auto ExternType::global() const -> const GlobalType* {
  return kind() == EXTERN_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(EXTERN_TABLE),
        element(std::move(element)),
        limits(limits) {}

  ~TableTypeImpl() {}
};

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

TableType::~TableType() {}

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

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

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

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

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

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

// Memory Types

struct MemoryTypeImpl : ExternTypeImpl {
  Limits limits;

  explicit MemoryTypeImpl(Limits limits)
      : ExternTypeImpl(EXTERN_MEMORY), limits(limits) {}

  ~MemoryTypeImpl() {}
};

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

MemoryType::~MemoryType() {}

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

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

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

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

auto ExternType::memory() const -> const MemoryType* {
  return kind() == EXTERN_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)) {}

  ~ImportTypeImpl() {}
};

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

ImportType::~ImportType() { impl(this)->~ImportTypeImpl(); }

void ImportType::operator delete(void* p) { ::operator delete(p); }

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*>();
}

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

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

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

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)) {}

  ~ExportTypeImpl() {}
};

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

ExportType::~ExportType() { impl(this)->~ExportTypeImpl(); }

void ExportType::operator delete(void* p) { ::operator delete(p); }

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*>();
}

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

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

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

///////////////////////////////////////////////////////////////////////////////
// Conversions of types from and to V8 objects

// Types

auto valtype_to_v8(StoreImpl* store, const ValType* type)
    -> v8::Local<v8::Value> {
  v8_string_t string;
  switch (type->kind()) {
    case I32:
      string = V8_S_I32;
      break;
    case I64:
      string = V8_S_I64;
      break;
    case F32:
      string = V8_S_F32;
      break;
    case F64:
      string = V8_S_F64;
      break;
    case ANYREF:
      string = V8_S_ANYREF;
      break;
    case FUNCREF:
      string = V8_S_ANYFUNC;
      break;
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
  return store->v8_string(string);
}

auto mutability_to_v8(StoreImpl* store, Mutability mutability)
    -> v8::Local<v8::Boolean> {
  return v8::Boolean::New(store->isolate(), mutability == VAR);
}

void limits_to_v8(StoreImpl* store, Limits limits, v8::Local<v8::Object> desc) {
  auto isolate = store->isolate();
  auto context = store->context();
  ignore(desc->DefineOwnProperty(
      context, store->v8_string(V8_S_MINIMUM),
      v8::Integer::NewFromUnsigned(isolate, limits.min)));
  if (limits.max != Limits(0).max) {
    ignore(desc->DefineOwnProperty(
        context, store->v8_string(V8_S_MAXIMUM),
        v8::Integer::NewFromUnsigned(isolate, limits.max)));
  }
}

auto globaltype_to_v8(StoreImpl* store, const GlobalType* type)
    -> v8::Local<v8::Object> {
  auto isolate = store->isolate();
  auto context = store->context();
  auto desc = v8::Object::New(isolate);
  ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_VALUE),
                                 valtype_to_v8(store, type->content())));
  ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_MUTABLE),
                                 mutability_to_v8(store, type->mutability())));
  return desc;
}

auto tabletype_to_v8(StoreImpl* store, const TableType* type)
    -> v8::Local<v8::Object> {
  auto isolate = store->isolate();
  auto context = store->context();
  auto desc = v8::Object::New(isolate);
  ignore(desc->DefineOwnProperty(context, store->v8_string(V8_S_ELEMENT),
                                 valtype_to_v8(store, type->element())));
  limits_to_v8(store, type->limits(), desc);
  return desc;
}

auto memorytype_to_v8(StoreImpl* store, const MemoryType* type)
    -> v8::Local<v8::Object> {
  auto isolate = store->isolate();
  auto desc = v8::Object::New(isolate);
  limits_to_v8(store, type->limits(), desc);
  return desc;
}

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

// Values

auto val_to_v8(StoreImpl* store, const Val& v) -> v8::Local<v8::Value> {
  auto isolate = store->isolate();
  switch (v.kind()) {
    case I32:
      return v8::Integer::NewFromUnsigned(isolate, v.i32());
    case I64:
      return v8::BigInt::New(isolate, v.i64());
    case F32:
      return v8::Number::New(isolate, v.f32());
    case F64:
      return v8::Number::New(isolate, v.f64());
    case ANYREF:
    case FUNCREF: {
      if (v.ref() == nullptr) {
        return v8::Null(isolate);
      } else {
        WASM_UNIMPLEMENTED("ref value");
      }
    }
    default:
      UNREACHABLE();
  }
}

auto v8_to_val(StoreImpl* store, v8::Local<v8::Value> value, const ValType* t)
    -> own<Val> {
  auto context = store->context();
  switch (t->kind()) {
    case I32:
      return Val(value->Int32Value(context).ToChecked());
    case I64: {
      auto bigint = value->ToBigInt(context).ToLocalChecked();
      return Val(bigint->Int64Value());
    }
    case F32: {
      auto number = value->NumberValue(context).ToChecked();
      return Val(static_cast<float32_t>(number));
    }
    case F64:
      return Val(value->NumberValue(context).ToChecked());
    case ANYREF:
    case FUNCREF: {
      if (value->IsNull()) {
        return Val(nullptr);
      } else {
        WASM_UNIMPLEMENTED("ref value");
      }
    }
  }
}

// References

template <class Ref>
class RefImpl : public v8::Persistent<v8::Object> {
 public:
  RefImpl() = delete;

  static auto make(StoreImpl* store, v8::Local<v8::Object> obj) -> own<Ref*> {
    static_assert(sizeof(RefImpl) == sizeof(v8::Persistent<v8::Object>),
                  "incompatible object layout");
    RefImpl* self =
        static_cast<RefImpl*>(new (std::nothrow) v8::Persistent<v8::Object>());
    if (!self) return nullptr;
    self->Reset(store->isolate(), obj);
    return make_own(seal<Ref>(self));
  }

  auto copy() const -> own<Ref*> {
    v8::HandleScope handle_scope(isolate());
    return make(store(), v8_object());
  }

  auto store() const -> StoreImpl* { return StoreImpl::get(isolate()); }

  auto isolate() const -> v8::Isolate* {
    return v8::wasm::object_isolate(*this);
  }

  auto v8_object() const -> v8::Local<v8::Object> { return Get(isolate()); }

  auto get_host_info() const -> void* {
    v8::HandleScope handle_scope(isolate());
    auto store = this->store();

    v8::Local<v8::Value> args[] = {v8_object()};
    auto maybe_result =
        store->v8_function(V8_F_WEAKMAP_GET)
            ->Call(store->context(), store->host_data_map(), 1, args);
    if (maybe_result.IsEmpty()) return nullptr;

    auto data = v8::wasm::foreign_get(maybe_result.ToLocalChecked());
    return reinterpret_cast<HostData*>(data)->info;
  }

  void set_host_info(void* info, void (*finalizer)(void*)) {
    v8::HandleScope handle_scope(isolate());
    auto store = this->store();

    // V8 attaches finalizers to handles instead of objects, and such handles
    // cannot be reused after the finalizer has been invoked.
    // So we need to create them separately from the pool.
    auto data = new HostData(store->isolate(), v8_object(), info, finalizer);
    data->handle.template SetWeak<HostData>(data, &v8_finalizer,
                                            v8::WeakCallbackType::kParameter);
    auto foreign = v8::wasm::foreign_new(store->isolate(), data);
    v8::Local<v8::Value> args[] = {v8_object(), foreign};
    auto maybe_result =
        store->v8_function(V8_F_WEAKMAP_SET)
            ->Call(store->context(), store->host_data_map(), 2, args);
    if (maybe_result.IsEmpty()) return;
  }

 private:
  struct HostData {
    HostData(v8::Isolate* isolate, v8::Local<v8::Object> object, void* info,
             void (*finalizer)(void*))
        : handle(isolate, object), info(info), finalizer(finalizer) {}
    v8::Persistent<v8::Object> handle;
    void* info;
    void (*finalizer)(void*);
  };

  static void v8_finalizer(const v8::WeakCallbackInfo<HostData>& info) {
    auto data = info.GetParameter();
    data->handle.Reset();  // Must reset weak handle before deleting it!
    if (data->finalizer) (*data->finalizer)(data->info);
    delete data;
  }
};

template <>
struct implement<Ref> {
  using type = RefImpl<Ref>;
};

Ref::~Ref() {
  v8::HandleScope handle_scope(impl(this)->isolate());
  impl(this)->Reset();
  delete impl(this);
}

void Ref::operator delete(void* p) {}

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

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

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

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

// Traps

template <>
struct implement<Trap> {
  using type = RefImpl<Trap>;
};

Trap::~Trap() {}

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

auto Trap::make(Store* store_abs, const Message& message) -> own<Trap*> {
  auto store = impl(store_abs);
  v8::Isolate* isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);

  auto maybe_string = v8::String::NewFromUtf8(isolate, message.get(),
                                              v8::NewStringType::kNormal,
                                              static_cast<int>(message.size()));
  if (maybe_string.IsEmpty()) return own<Trap*>();
  auto exception = v8::Exception::Error(maybe_string.ToLocalChecked());
  return RefImpl<Trap>::make(store, v8::Local<v8::Object>::Cast(exception));
}

auto Trap::message() const -> Message {
  auto isolate = impl(this)->isolate();
  v8::HandleScope handle_scope(isolate);

  auto message = v8::Exception::CreateMessage(isolate, impl(this)->v8_object());
  v8::String::Utf8Value string(isolate, message->Get());
  return vec<byte_t>::make(std::string(*string));
}

// Foreign Objects

template <>
struct implement<Foreign> {
  using type = RefImpl<Foreign>;
};

Foreign::~Foreign() {}

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

auto Foreign::make(Store* store_abs) -> own<Foreign*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);

  auto obj = v8::Object::New(isolate);
  return RefImpl<Foreign>::make(store, obj);
}

// Modules

template <>
struct implement<Module> {
  using type = RefImpl<Module>;
};

Module::~Module() {}

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

auto Module::validate(Store* store_abs, const vec<byte_t>& binary) -> bool {
  auto store = impl(store_abs);
  v8::Isolate* isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);

  auto array_buffer = v8::ArrayBuffer::New(
      isolate, const_cast<byte_t*>(binary.get()), binary.size());

  v8::Local<v8::Value> args[] = {array_buffer};
  auto result = store->v8_function(V8_F_VALIDATE)
                    ->Call(store->context(), v8::Undefined(isolate), 1, args);
  if (result.IsEmpty()) return false;

  return result.ToLocalChecked()->IsTrue();
}

auto Module::make(Store* store_abs, const vec<byte_t>& binary) -> own<Module*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  auto context = store->context();
  v8::HandleScope handle_scope(isolate);

  auto array_buffer = v8::ArrayBuffer::New(
      isolate, const_cast<byte_t*>(binary.get()), binary.size());

  v8::Local<v8::Value> args[] = {array_buffer};
  auto maybe_obj =
      store->v8_function(V8_F_MODULE)->NewInstance(context, 1, args);
  if (maybe_obj.IsEmpty()) return nullptr;
  return RefImpl<Module>::make(store, maybe_obj.ToLocalChecked());
}

auto Module::imports() const -> vec<ImportType*> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto module = impl(this)->v8_object();
  auto binary =
      vec<byte_t>::adopt(v8::wasm::module_binary_size(module),
                         const_cast<byte_t*>(v8::wasm::module_binary(module)));
  auto imports = wasm::bin::imports(binary);
  binary.release();
  return imports;
}

auto Module::exports() const -> vec<ExportType*> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto module = impl(this)->v8_object();
  auto binary =
      vec<byte_t>::adopt(v8::wasm::module_binary_size(module),
                         const_cast<byte_t*>(v8::wasm::module_binary(module)));
  auto exports = wasm::bin::exports(binary);
  binary.release();
  return exports;
}

auto Module::serialize() const -> vec<byte_t> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto module = impl(this)->v8_object();
  auto binary_size = v8::wasm::module_binary_size(module);
  auto serial_size = v8::wasm::module_serialize_size(module);
  auto size_size = i::wasm::LEBHelper::sizeof_u64v(binary_size);
  auto buffer =
      vec<byte_t>::make_uninitialized(size_size + binary_size + serial_size);
  auto ptr = buffer.get();
  i::wasm::LEBHelper::write_u64v(reinterpret_cast<uint8_t**>(&ptr),
                                 binary_size);
  std::memcpy(ptr, v8::wasm::module_binary(module), binary_size);
  ptr += binary_size;
  if (!v8::wasm::module_serialize(module, ptr, serial_size)) buffer.reset();
  return std::move(buffer);
}

auto Module::deserialize(Store* store_abs, const vec<byte_t>& serialized)
    -> own<Module*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);
  auto ptr = serialized.get();
  auto binary_size = wasm::bin::u64(ptr);
  auto size_size = ptr - serialized.get();
  auto serial_size = serialized.size() - size_size - binary_size;
  auto maybe_obj = v8::wasm::module_deserialize(isolate, ptr, binary_size,
                                                ptr + binary_size, serial_size);
  if (maybe_obj.IsEmpty()) return nullptr;
  return RefImpl<Module>::make(store, maybe_obj.ToLocalChecked());
}

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

template <>
Shared<Module>::~Shared() {
  impl(this)->~vec();
}

template <>
void Shared<Module>::operator delete(void* p) {
  ::operator delete(p);
}

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

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>;
};

Extern::~Extern() {}

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

auto Extern::kind() const -> ExternKind {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::extern_kind(impl(this)->v8_object());
}

auto Extern::type() const -> own<ExternType*> {
  switch (kind()) {
    case EXTERN_FUNC:
      return func()->type();
    case EXTERN_GLOBAL:
      return global()->type();
    case EXTERN_TABLE:
      return table()->type();
    case EXTERN_MEMORY:
      return memory()->type();
  }
}

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

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

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

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

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

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

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

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

auto extern_to_v8(const Extern* ex) -> v8::Local<v8::Value> {
  return impl(ex)->v8_object();
}

// Function Instances

template <>
struct implement<Func> {
  using type = RefImpl<Func>;
};

Func::~Func() {}

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

struct FuncData {
  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 void v8_callback(const v8::FunctionCallbackInfo<v8::Value>&);
  static void finalize_func_data(void* data);
};

namespace {

auto make_func(Store* store_abs, FuncData* data) -> own<Func*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);
  auto context = store->context();

  // Create V8 function
  auto v8_data = v8::wasm::foreign_new(isolate, data);
  auto function_template =
      v8::FunctionTemplate::New(isolate, &FuncData::v8_callback, v8_data);
  auto maybe_func_obj = function_template->GetFunction(context);
  if (maybe_func_obj.IsEmpty()) return own<Func*>();
  auto func_obj = maybe_func_obj.ToLocalChecked();

  // Create wrapper instance
  auto binary = wasm::bin::wrapper(data->type.get());
  auto module = Module::make(store_abs, binary);

  auto imports_obj = v8::Object::New(isolate);
  auto module_obj = v8::Object::New(isolate);
  auto str = store->v8_string(V8_S_EMPTY);
  ignore(imports_obj->DefineOwnProperty(context, str, module_obj));
  ignore(module_obj->DefineOwnProperty(context, str, func_obj));

  v8::Local<v8::Value> instantiate_args[] = {impl(module.get())->v8_object(),
                                             imports_obj};
  auto instance_obj = store->v8_function(V8_F_INSTANCE)
                          ->NewInstance(context, 2, instantiate_args)
                          .ToLocalChecked();
  assert(!instance_obj.IsEmpty());
  assert(instance_obj->IsObject());
  auto exports_obj = v8::wasm::instance_exports(instance_obj);
  assert(!exports_obj.IsEmpty());
  assert(exports_obj->IsObject());
  auto wrapped_func_obj = v8::Local<v8::Function>::Cast(
      exports_obj->Get(context, str).ToLocalChecked());
  assert(!wrapped_func_obj.IsEmpty());
  assert(wrapped_func_obj->IsFunction());

  auto func = RefImpl<Func>::make(store, wrapped_func_obj);
  func->set_host_info(data, &FuncData::finalize_func_data);
  return func;
}

auto func_type(v8::Local<v8::Object> v8_func) -> own<FuncType*> {
  auto param_arity = v8::wasm::func_type_param_arity(v8_func);
  auto result_arity = v8::wasm::func_type_result_arity(v8_func);
  auto params = vec<ValType*>::make_uninitialized(param_arity);
  auto results = vec<ValType*>::make_uninitialized(result_arity);

  for (size_t i = 0; i < params.size(); ++i) {
    auto kind = v8::wasm::func_type_param(v8_func, i);
    params[i] = ValType::make(kind);
  }
  for (size_t i = 0; i < results.size(); ++i) {
    auto kind = v8::wasm::func_type_result(v8_func, i);
    results[i] = ValType::make(kind);
  }

  return FuncType::make(std::move(params), std::move(results));
}

}  // namespace

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

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

auto Func::type() const -> own<FuncType*> {
  // return impl(this)->data->type->copy();
  v8::HandleScope handle_scope(impl(this)->isolate());
  return func_type(impl(this)->v8_object());
}

auto Func::param_arity() const -> size_t {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::func_type_param_arity(impl(this)->v8_object());
}

auto Func::result_arity() const -> size_t {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::func_type_result_arity(impl(this)->v8_object());
}

auto Func::call(const Val args[], Val results[]) const -> own<Trap*> {
  auto func = impl(this);
  auto store = func->store();
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);

  auto context = store->context();
  auto type = this->type();
  auto& param_types = type->params();
  auto& result_types = type->results();

  // TODO(rossberg): cache v8_args array per thread.
  auto v8_args = std::unique_ptr<v8::Local<v8::Value>[]>(
      new (std::nothrow) v8::Local<v8::Value>[param_types.size()]);
  for (size_t i = 0; i < param_types.size(); ++i) {
    assert(args[i].kind() == param_types[i]->kind());
    v8_args[i] = val_to_v8(store, args[i]);
  }

  v8::TryCatch handler(isolate);
  auto v8_function = v8::Local<v8::Function>::Cast(func->v8_object());
  auto maybe_val =
      v8_function->Call(context, v8::Undefined(isolate),
                        static_cast<int>(param_types.size()), v8_args.get());

  if (handler.HasCaught()) {
    auto exception = handler.Exception();
    if (!exception->IsObject()) {
      auto maybe_string = exception->ToString(store->context());
      auto string = maybe_string.IsEmpty() ? store->v8_string(V8_S_EMPTY)
                                           : maybe_string.ToLocalChecked();
      exception = v8::Exception::Error(string);
    }
    return RefImpl<Trap>::make(store, v8::Local<v8::Object>::Cast(exception));
  }

  auto val = maybe_val.ToLocalChecked();
  if (result_types.size() == 0) {
    assert(val->IsUndefined());
  } else if (result_types.size() == 1) {
    assert(!val->IsUndefined());
    new (&results[0]) Val(v8_to_val(store, val, result_types[0]));
  } else {
    WASM_UNIMPLEMENTED("multiple results");
  }
  return nullptr;
}

void FuncData::v8_callback(const v8::FunctionCallbackInfo<v8::Value>& info) {
  auto self = reinterpret_cast<FuncData*>(v8::wasm::foreign_get(info.Data()));
  auto store = impl(self->store);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);

  auto& param_types = self->type->params();
  auto& result_types = self->type->results();

  assert(param_types.size() == static_cast<size_t>(info.Length()));
  int num_param_types = static_cast<int>(param_types.size());
  int num_result_types = static_cast<int>(result_types.size());

  // TODO(rossberg): cache params and result arrays per thread.
  std::unique_ptr<Val[]> args(new Val[num_param_types]);
  std::unique_ptr<Val[]> results(new Val[num_result_types]);
  for (int i = 0; i < num_param_types; ++i) {
    args[i] = v8_to_val(store, info[i], param_types[i]);
  }

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

  if (trap) {
    isolate->ThrowException(impl(trap.get())->v8_object());
    return;
  }

  auto ret = info.GetReturnValue();
  if (result_types.size() == 0) {
    ret.SetUndefined();
  } else if (result_types.size() == 1) {
    assert(results[0].kind() == result_types[0]->kind());
    ret.Set(val_to_v8(store, results[0]));
  } else {
    WASM_UNIMPLEMENTED("multiple results");
  }
}

void FuncData::finalize_func_data(void* data) {
  delete reinterpret_cast<FuncData*>(data);
}

// Global Instances

template <>
struct implement<Global> {
  using type = RefImpl<Global>;
};

Global::~Global() {}

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

auto Global::make(Store* store_abs, const GlobalType* type, const Val& val)
    -> own<Global*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);
  auto context = store->context();

  assert(type->content()->kind() == val.kind());

  // Create wrapper instance
  auto binary = wasm::bin::wrapper(type);
  auto module = Module::make(store_abs, binary);

  v8::Local<v8::Value> instantiate_args[] = {impl(module.get())->v8_object()};
  auto instance_obj = store->v8_function(V8_F_INSTANCE)
                          ->NewInstance(context, 1, instantiate_args)
                          .ToLocalChecked();
  auto exports_obj = v8::wasm::instance_exports(instance_obj);
  auto obj = v8::Local<v8::Object>::Cast(
      exports_obj->Get(context, store->v8_string(V8_S_EMPTY)).ToLocalChecked());
  assert(!obj.IsEmpty() && obj->IsObject());

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

auto Global::type() const -> own<GlobalType*> {
  // return impl(this)->data->type->copy();
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto v8_global = impl(this)->v8_object();
  auto kind = v8::wasm::global_type_content(v8_global);
  auto mutability = v8::wasm::global_type_mutable(v8_global) ? VAR : CONST;
  return GlobalType::make(ValType::make(kind), mutability);
}

auto Global::get() const -> Val {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto v8_global = impl(this)->v8_object();
  switch (type()->content()->kind()) {
    case I32:
      return Val(v8::wasm::global_get_i32(v8_global));
    case I64:
      return Val(v8::wasm::global_get_i64(v8_global));
    case F32:
      return Val(v8::wasm::global_get_f32(v8_global));
    case F64:
      return Val(v8::wasm::global_get_f64(v8_global));
    case ANYREF:
    case FUNCREF:
      WASM_UNIMPLEMENTED("globals of reference type");
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
}

void Global::set(const Val& val) {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto v8_global = impl(this)->v8_object();
  switch (val.kind()) {
    case I32:
      return v8::wasm::global_set_i32(v8_global, val.i32());
    case I64:
      return v8::wasm::global_set_i64(v8_global, val.i64());
    case F32:
      return v8::wasm::global_set_f32(v8_global, val.f32());
    case F64:
      return v8::wasm::global_set_f64(v8_global, val.f64());
    case ANYREF:
    case FUNCREF:
      WASM_UNIMPLEMENTED("globals of reference type");
    default:
      // TODO(wasm+): support new value types
      UNREACHABLE();
  }
}

// Table Instances

template <>
struct implement<Table> {
  using type = RefImpl<Table>;
};

Table::~Table() {}

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

auto Table::make(Store* store_abs, const TableType* type, const Ref* ref)
    -> own<Table*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);
  auto context = store->context();

  v8::Local<v8::Value> init = v8::Null(isolate);
  if (ref) init = impl(ref)->v8_object();
  v8::Local<v8::Value> args[] = {tabletype_to_v8(store, type), init};
  auto maybe_obj =
      store->v8_function(V8_F_TABLE)->NewInstance(context, 2, args);
  if (maybe_obj.IsEmpty()) return own<Table*>();
  auto table = RefImpl<Table>::make(store, maybe_obj.ToLocalChecked());
  // TODO(wasm+): pass reference initialiser as parameter
  if (table && ref) {
    auto size = type->limits().min;
    auto obj = maybe_obj.ToLocalChecked();
    auto maybe_func =
        v8::MaybeLocal<v8::Function>(v8::Local<v8::Function>::Cast(init));
    for (size_t i = 0; i < size; ++i) {
      v8::wasm::table_set(obj, i, maybe_func);
    }
  }
  return table;
}

auto Table::type() const -> own<TableType*> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto v8_table = impl(this)->v8_object();
  uint32_t min = v8::wasm::table_type_min(v8_table);
  uint32_t max = v8::wasm::table_type_max(v8_table);
  // TODO(wasm+): support new element types.
  return TableType::make(ValType::make(FUNCREF), Limits(min, max));
}

auto Table::get(size_t index) const -> own<Ref*> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto maybe = v8::wasm::table_get(impl(this)->v8_object(), index);
  if (maybe.IsEmpty() || maybe.ToLocalChecked()->IsNull()) return own<Ref*>();
  // TODO(wasm+): other references
  auto obj = maybe.ToLocalChecked();
  assert(obj->IsFunction());
  return RefImpl<Func>::make(impl(this)->store(), obj);
}

auto Table::set(size_t index, const Ref* ref) -> bool {
  v8::HandleScope handle_scope(impl(this)->isolate());
  if (ref && !impl(ref)->v8_object()->IsFunction()) {
    WASM_UNIMPLEMENTED("non-function table elements");
  }
  auto obj = ref ? v8::MaybeLocal<v8::Function>(
                       v8::Local<v8::Function>::Cast(impl(ref)->v8_object()))
                 : v8::MaybeLocal<v8::Function>();
  return v8::wasm::table_set(impl(this)->v8_object(), index, obj);
}

auto Table::size() const -> size_t {
  v8::HandleScope handle_scope(impl(this)->isolate());
  // TODO(jkummerow): Having Table::size_t shadowing "std" size_t is ugly.
  return static_cast<Table::size_t>(
      v8::wasm::table_size(impl(this)->v8_object()));
}

auto Table::grow(size_t delta, const Ref* ref) -> bool {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto obj = ref ? v8::MaybeLocal<v8::Function>(
                       v8::Local<v8::Function>::Cast(impl(ref)->v8_object()))
                 : v8::MaybeLocal<v8::Function>();
  return v8::wasm::table_grow(impl(this)->v8_object(), delta, obj);
}

// Memory Instances

template <>
struct implement<Memory> {
  using type = RefImpl<Memory>;
};

Memory::~Memory() {}

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

auto Memory::make(Store* store_abs, const MemoryType* type) -> own<Memory*> {
  auto store = impl(store_abs);
  auto isolate = store->isolate();
  v8::HandleScope handle_scope(isolate);
  auto context = store->context();

  v8::Local<v8::Value> args[] = {memorytype_to_v8(store, type)};
  auto maybe_obj =
      store->v8_function(V8_F_MEMORY)->NewInstance(context, 1, args);
  if (maybe_obj.IsEmpty()) return own<Memory*>();
  return RefImpl<Memory>::make(store, maybe_obj.ToLocalChecked());
}

auto Memory::type() const -> own<MemoryType*> {
  v8::HandleScope handle_scope(impl(this)->isolate());
  auto v8_memory = impl(this)->v8_object();
  uint32_t min = v8::wasm::memory_type_min(v8_memory);
  uint32_t max = v8::wasm::memory_type_max(v8_memory);
  return MemoryType::make(Limits(min, max));
}

auto Memory::data() const -> byte_t* {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::memory_data(impl(this)->v8_object());
}

auto Memory::data_size() const -> size_t {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::memory_data_size(impl(this)->v8_object());
}

auto Memory::size() const -> pages_t {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::memory_size(impl(this)->v8_object());
}

auto Memory::grow(pages_t delta) -> bool {
  v8::HandleScope handle_scope(impl(this)->isolate());
  return v8::wasm::memory_grow(impl(this)->v8_object(), delta);
}

// Module Instances

template <>
struct implement<Instance> {
  using type = RefImpl<Instance>;
};

Instance::~Instance() {}

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

auto Instance::make(Store* store_abs, const Module* module_abs,
                    const Extern* const imports[]) -> own<Instance*> {
  auto store = impl(store_abs);
  auto module = impl(module_abs);
  auto isolate = store->isolate();
  auto context = store->context();
  v8::HandleScope handle_scope(isolate);

  assert(module->v8_object()->GetIsolate() == isolate);

  auto import_types = module_abs->imports();
  auto imports_obj = v8::Object::New(isolate);
  for (size_t i = 0; i < import_types.size(); ++i) {
    auto type = import_types[i];
    auto maybe_module = v8::String::NewFromOneByte(
        isolate, reinterpret_cast<const uint8_t*>(type->module().get()),
        v8::NewStringType::kNormal, static_cast<int>(type->module().size()));
    if (maybe_module.IsEmpty()) return own<Instance*>();
    auto module_str = maybe_module.ToLocalChecked();
    auto maybe_name = v8::String::NewFromOneByte(
        isolate, reinterpret_cast<const uint8_t*>(type->name().get()),
        v8::NewStringType::kNormal, static_cast<int>(type->name().size()));
    if (maybe_name.IsEmpty()) return own<Instance*>();
    auto name_str = maybe_name.ToLocalChecked();

    v8::Local<v8::Object> module_obj;
    if (imports_obj->HasOwnProperty(context, module_str).ToChecked()) {
      module_obj = v8::Local<v8::Object>::Cast(
          imports_obj->Get(context, module_str).ToLocalChecked());
    } else {
      module_obj = v8::Object::New(isolate);
      ignore(imports_obj->DefineOwnProperty(context, module_str, module_obj));
    }

    ignore(module_obj->DefineOwnProperty(context, name_str,
                                         extern_to_v8(imports[i])));
  }

  v8::Local<v8::Value> instantiate_args[] = {module->v8_object(), imports_obj};
  auto obj = store->v8_function(V8_F_INSTANCE)
                 ->NewInstance(context, 2, instantiate_args)
                 .ToLocalChecked();
  return RefImpl<Instance>::make(store, obj);
}

auto Instance::exports() const -> vec<Extern*> {
  auto instance = impl(this);
  auto store = instance->store();
  auto isolate = store->isolate();
  auto context = store->context();
  v8::HandleScope handle_scope(isolate);

  auto module_obj = v8::wasm::instance_module(instance->v8_object());
  auto exports_obj = v8::wasm::instance_exports(instance->v8_object());
  assert(!module_obj.IsEmpty() && module_obj->IsObject());
  assert(!exports_obj.IsEmpty() && exports_obj->IsObject());

  auto module = RefImpl<Module>::make(store, module_obj);
  auto export_types = module->exports();
  auto exports = vec<Extern*>::make_uninitialized(export_types.size());
  if (!exports) return vec<Extern*>::invalid();

  for (size_t i = 0; i < export_types.size(); ++i) {
    auto& name = export_types[i]->name();
    auto maybe_name_obj =
        v8::String::NewFromUtf8(isolate, name.get(), v8::NewStringType::kNormal,
                                static_cast<int>(name.size()));
    if (maybe_name_obj.IsEmpty()) return vec<Extern*>::invalid();
    auto name_obj = maybe_name_obj.ToLocalChecked();
    auto obj = v8::Local<v8::Object>::Cast(
        exports_obj->Get(context, name_obj).ToLocalChecked());

    auto type = export_types[i]->type();
    assert(type->kind() == v8::wasm::extern_kind(obj));
    switch (type->kind()) {
      case EXTERN_FUNC: {
        exports[i].reset(RefImpl<Func>::make(store, obj));
      } break;
      case EXTERN_GLOBAL: {
        exports[i].reset(RefImpl<Global>::make(store, obj));
      } break;
      case EXTERN_TABLE: {
        exports[i].reset(RefImpl<Table>::make(store, obj));
      } break;
      case EXTERN_MEMORY: {
        exports[i].reset(RefImpl<Memory>::make(store, 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* x)->wasm_##name##_t* {                 \
    return static_cast<wasm_##name##_t*>(x);                                 \
  }                                                                          \
  extern "C++" inline auto hide(const Name* x)->const wasm_##name##_t* {     \
    return static_cast<const wasm_##name##_t*>(x);                           \
  }                                                                          \
  extern "C++" inline auto reveal(wasm_##name##_t* x)->Name* { return x; }   \
  extern "C++" inline auto reveal(const wasm_##name##_t* x)->const Name* {   \
    return x;                                                                \
  }                                                                          \
  extern "C++" inline auto get(wasm::own<Name*>& x)->wasm_##name##_t* {      \
    return hide(x.get());                                                    \
  }                                                                          \
  extern "C++" inline auto get(const wasm::own<Name*>& x)                    \
      ->const wasm_##name##_t* {                                             \
    return hide(x.get());                                                    \
  }                                                                          \
  extern "C++" inline auto release(wasm::own<Name*>&& x)->wasm_##name##_t* { \
    return hide(x.release());                                                \
  }                                                                          \
  extern "C++" inline auto adopt(wasm_##name##_t* x)->wasm::own<Name*> {     \
    return make_own(x);                                                      \
  }

// Vectors

#define WASM_DEFINE_VEC_BASE(name, Name, ptr_or_none)                       \
  extern "C++" inline auto hide(wasm::vec<Name ptr_or_none>& v)             \
      ->wasm_##name##_vec_t* {                                              \
    static_assert(sizeof(wasm_##name##_vec_t) == sizeof(wasm::vec<Name>),   \
                  "C/C++ incompatibility");                                 \
    return reinterpret_cast<wasm_##name##_vec_t*>(&v);                      \
  }                                                                         \
  extern "C++" inline auto hide(const wasm::vec<Name ptr_or_none>& v)       \
      ->const wasm_##name##_vec_t* {                                        \
    static_assert(sizeof(wasm_##name##_vec_t) == sizeof(wasm::vec<Name>),   \
                  "C/C++ incompatibility");                                 \
    return reinterpret_cast<const wasm_##name##_vec_t*>(&v);                \
  }                                                                         \
  extern "C++" inline auto hide(Name ptr_or_none* v)                        \
      ->wasm_##name##_t ptr_or_none* {                                      \
    static_assert(                                                          \
        sizeof(wasm_##name##_t ptr_or_none) == sizeof(Name ptr_or_none),    \
        "C/C++ incompatibility");                                           \
    return reinterpret_cast<wasm_##name##_t ptr_or_none*>(v);               \
  }                                                                         \
  extern "C++" inline auto hide(Name ptr_or_none const* v)                  \
      ->wasm_##name##_t ptr_or_none const* {                                \
    static_assert(                                                          \
        sizeof(wasm_##name##_t ptr_or_none) == sizeof(Name ptr_or_none),    \
        "C/C++ incompatibility");                                           \
    return reinterpret_cast<wasm_##name##_t ptr_or_none const*>(v);         \
  }                                                                         \
  extern "C++" inline auto reveal(wasm_##name##_t ptr_or_none* v)           \
      ->Name ptr_or_none* {                                                 \
    static_assert(                                                          \
        sizeof(wasm_##name##_t ptr_or_none) == sizeof(Name ptr_or_none),    \
        "C/C++ incompatibility");                                           \
    return reinterpret_cast<Name ptr_or_none*>(v);                          \
  }                                                                         \
  extern "C++" inline auto reveal(wasm_##name##_t ptr_or_none const* v)     \
      ->Name ptr_or_none const* {                                           \
    static_assert(                                                          \
        sizeof(wasm_##name##_t ptr_or_none) == sizeof(Name ptr_or_none),    \
        "C/C++ incompatibility");                                           \
    return reinterpret_cast<Name ptr_or_none const*>(v);                    \
  }                                                                         \
  extern "C++" inline auto get(wasm::vec<Name ptr_or_none>& v)              \
      ->wasm_##name##_vec_t {                                               \
    wasm_##name##_vec_t v2 = {v.size(), hide(v.get())};                     \
    return v2;                                                              \
  }                                                                         \
  extern "C++" inline auto get(const wasm::vec<Name ptr_or_none>& v)        \
      ->const wasm_##name##_vec_t {                                         \
    wasm_##name##_vec_t v2 = {                                              \
        v.size(), const_cast<wasm_##name##_t ptr_or_none*>(hide(v.get()))}; \
    return v2;                                                              \
  }                                                                         \
  extern "C++" inline auto release(wasm::vec<Name ptr_or_none>&& v)         \
      ->wasm_##name##_vec_t {                                               \
    wasm_##name##_vec_t v2 = {v.size(), hide(v.release())};                 \
    return v2;                                                              \
  }                                                                         \
  extern "C++" inline auto adopt(wasm_##name##_vec_t* v)                    \
      ->wasm::vec<Name ptr_or_none> {                                       \
    return wasm::vec<Name ptr_or_none>::adopt(v->size, reveal(v->data));    \
  }                                                                         \
  extern "C++" inline auto borrow(const wasm_##name##_vec_t* v)             \
      ->borrowed_vec<Name ptr_or_none> {                                    \
    return borrowed_vec<Name ptr_or_none>(                                  \
        wasm::vec<Name ptr_or_none>::adopt(v->size, reveal(v->data)));      \
  }                                                                         \
                                                                            \
  void wasm_##name##_vec_new_uninitialized(wasm_##name##_vec_t* out,        \
                                           size_t size) {                   \
    *out = release(wasm::vec<Name ptr_or_none>::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(v); }

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

// Vectors who own their elements
#define WASM_DEFINE_VEC(name, Name, ptr_or_none)                         \
  WASM_DEFINE_VEC_BASE(name, Name, ptr_or_none)                          \
                                                                         \
  void wasm_##name##_vec_new(wasm_##name##_vec_t* out, size_t size,      \
                             wasm_##name##_t ptr_or_none const data[]) { \
    auto v2 = wasm::vec<Name ptr_or_none>::make_uninitialized(size);     \
    for (size_t i = 0; i < v2.size(); ++i) {                             \
      v2[i] = adopt(data[i]);                                            \
    }                                                                    \
    *out = release(std::move(v2));                                       \
  }                                                                      \
                                                                         \
  void wasm_##name##_vec_copy(wasm_##name##_vec_t* out,                  \
                              wasm_##name##_vec_t* v) {                  \
    auto v2 = wasm::vec<Name ptr_or_none>::make_uninitialized(v->size);  \
    for (size_t i = 0; i < v2.size(); ++i) {                             \
      v2[i] = adopt(wasm_##name##_copy(v->data[i]));                     \
    }                                                                    \
    *out = release(std::move(v2));                                       \
  }

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

// Byte vectors

using byte = byte_t;
WASM_DEFINE_VEC_PLAIN(byte, byte, )

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

// Configuration

WASM_DEFINE_OWN(config, wasm::Config)

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

// Engine

WASM_DEFINE_OWN(engine, wasm::Engine)

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

wasm_engine_t* wasm_engine_new_with_config(wasm_config_t* config) {
  return release(wasm::Engine::make(adopt(config)));
}

// Stores

WASM_DEFINE_OWN(store, wasm::Store)

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

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

// Type attributes

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

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

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

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

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

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

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

extern "C++" inline auto reveal(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(name, Name, *)                            \
                                                            \
  wasm_##name##_t* wasm_##name##_copy(wasm_##name##_t* t) { \
    return release(t->copy());                              \
  }

// Value Types

WASM_DEFINE_TYPE(valtype, wasm::ValType)

wasm_valtype_t* wasm_valtype_new(wasm_valkind_t k) {
  return release(wasm::ValType::make(reveal(k)));
}

wasm_valkind_t wasm_valtype_kind(const wasm_valtype_t* t) {
  return hide(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(wasm::FuncType::make(adopt(params), adopt(results)));
}

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

const wasm_valtype_vec_t* wasm_functype_results(const wasm_functype_t* ft) {
  return hide(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(wasm::GlobalType::make(adopt(content), reveal(mutability)));
}

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

wasm_mutability_t wasm_globaltype_mutability(const wasm_globaltype_t* gt) {
  return hide(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(wasm::TableType::make(adopt(element), reveal(*limits)));
}

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

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

// Memory Types

WASM_DEFINE_TYPE(memorytype, wasm::MemoryType)

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

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

// Extern Types

WASM_DEFINE_TYPE(externtype, wasm::ExternType)

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

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

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

wasm_functype_t* wasm_externtype_as_functype(wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_FUNC
             ? hide(static_cast<wasm::FuncType*>(reveal(et)))
             : nullptr;
}
wasm_globaltype_t* wasm_externtype_as_globaltype(wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_GLOBAL
             ? hide(static_cast<wasm::GlobalType*>(reveal(et)))
             : nullptr;
}
wasm_tabletype_t* wasm_externtype_as_tabletype(wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_TABLE
             ? hide(static_cast<wasm::TableType*>(reveal(et)))
             : nullptr;
}
wasm_memorytype_t* wasm_externtype_as_memorytype(wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_MEMORY
             ? hide(static_cast<wasm::MemoryType*>(reveal(et)))
             : nullptr;
}

const wasm_functype_t* wasm_externtype_as_functype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_FUNC
             ? hide(static_cast<const wasm::FuncType*>(reveal(et)))
             : nullptr;
}
const wasm_globaltype_t* wasm_externtype_as_globaltype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_GLOBAL
             ? hide(static_cast<const wasm::GlobalType*>(reveal(et)))
             : nullptr;
}
const wasm_tabletype_t* wasm_externtype_as_tabletype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_TABLE
             ? hide(static_cast<const wasm::TableType*>(reveal(et)))
             : nullptr;
}
const wasm_memorytype_t* wasm_externtype_as_memorytype_const(
    const wasm_externtype_t* et) {
  return et->kind() == wasm::EXTERN_MEMORY
             ? hide(static_cast<const wasm::MemoryType*>(reveal(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(
      wasm::ImportType::make(adopt(module), adopt(name), adopt(type)));
}

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

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

const wasm_externtype_t* wasm_importtype_type(const wasm_importtype_t* it) {
  return hide(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(wasm::ExportType::make(adopt(name), adopt(type)));
}

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

const wasm_externtype_t* wasm_exporttype_type(const wasm_exporttype_t* et) {
  return hide(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(t->copy());                                       \
  }                                                                  \
                                                                     \
  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(static_cast<wasm::Ref*>(reveal(r)));                       \
  }                                                                        \
  wasm_##name##_t* wasm_ref_as_##name(wasm_ref_t* r) {                     \
    return hide(static_cast<Name*>(reveal(r)));                            \
  }                                                                        \
                                                                           \
  const wasm_ref_t* wasm_##name##_as_ref_const(const wasm_##name##_t* r) { \
    return hide(static_cast<const wasm::Ref*>(reveal(r)));                 \
  }                                                                        \
  const wasm_##name##_t* wasm_ref_as_##name##_const(const wasm_ref_t* r) { \
    return hide(static_cast<const Name*>(reveal(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(v.kind)) || !v.of.ref;
}

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

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

inline auto adopt(wasm_val_t v) -> wasm::Val {
  switch (reveal(v.kind)) {
    case wasm::I32:
      return wasm::Val(v.of.i32);
    case wasm::I64:
      return wasm::Val(v.of.i64);
    case wasm::F32:
      return wasm::Val(v.of.f32);
    case wasm::F64:
      return wasm::Val(v.of.f64);
    case wasm::ANYREF:
    case wasm::FUNCREF:
      return wasm::Val(adopt(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();
  }
};

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

}  // extern "C++"

WASM_DEFINE_VEC_BASE(val, wasm::Val, )

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(data[i]);
  }
  *out = release(std::move(v2));
}

void wasm_val_vec_copy(wasm_val_vec_t* out, 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);
  }
  *out = release(std::move(v2));
}

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

void wasm_val_copy(wasm_val_t* out, const wasm_val_t* v) {
  *out = *v;
  if (is_ref(reveal(v->kind))) {
    out->of.ref = release(v->of.ref->copy());
  }
}

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

// 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(message);
  return release(wasm::Trap::make(store, message_.it));
}

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

// Foreign Objects

WASM_DEFINE_REF(foreign, wasm::Foreign)

wasm_foreign_t* wasm_foreign_new(wasm_store_t* store) {
  return release(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(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(binary);
  return release(wasm::Module::make(store, binary_.it));
}

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

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

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

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

wasm_shared_module_t* wasm_module_share(const wasm_module_t* module) {
  return release(reveal(module)->share());
}

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

// Function Instances

WASM_DEFINE_REF(func, wasm::Func)

extern "C++" {

auto wasm_callback(void* env, const wasm::Val args[], wasm::Val results[])
    -> wasm::own<wasm::Trap*> {
  auto f = reinterpret_cast<wasm_func_callback_t>(env);
  return adopt(f(hide(args), hide(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::Val args[],
                            wasm::Val results[]) -> wasm::own<wasm::Trap*> {
  auto t = static_cast<wasm_callback_env_t*>(env);
  return adopt(t->callback(t->env, hide(args), hide(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(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(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(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_trap_t* wasm_func_call(const wasm_func_t* func, const wasm_val_t args[],
                            wasm_val_t results[]) {
  return release(func->call(reveal(args), reveal(results)));
}

// 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);
  return release(wasm::Global::make(store, type, val_.it));
}

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

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

void wasm_global_set(wasm_global_t* global, const wasm_val_t* val) {
  auto val_ = borrow(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(wasm::Table::make(store, type, ref));
}

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

wasm_ref_t* wasm_table_get(const wasm_table_t* table, wasm_table_size_t index) {
  return release(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(wasm::Memory::make(store, type));
}

wasm_memorytype_t* wasm_memory_type(const wasm_memory_t* memory) {
  return release(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(extern, wasm::Extern, *)

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

wasm_extern_t* wasm_func_as_extern(wasm_func_t* func) {
  return hide(static_cast<wasm::Extern*>(reveal(func)));
}
wasm_extern_t* wasm_global_as_extern(wasm_global_t* global) {
  return hide(static_cast<wasm::Extern*>(reveal(global)));
}
wasm_extern_t* wasm_table_as_extern(wasm_table_t* table) {
  return hide(static_cast<wasm::Extern*>(reveal(table)));
}
wasm_extern_t* wasm_memory_as_extern(wasm_memory_t* memory) {
  return hide(static_cast<wasm::Extern*>(reveal(memory)));
}

const wasm_extern_t* wasm_func_as_extern_const(const wasm_func_t* func) {
  return hide(static_cast<const wasm::Extern*>(reveal(func)));
}
const wasm_extern_t* wasm_global_as_extern_const(const wasm_global_t* global) {
  return hide(static_cast<const wasm::Extern*>(reveal(global)));
}
const wasm_extern_t* wasm_table_as_extern_const(const wasm_table_t* table) {
  return hide(static_cast<const wasm::Extern*>(reveal(table)));
}
const wasm_extern_t* wasm_memory_as_extern_const(const wasm_memory_t* memory) {
  return hide(static_cast<const wasm::Extern*>(reveal(memory)));
}

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

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

// Module Instances

WASM_DEFINE_REF(instance, wasm::Instance)

wasm_instance_t* wasm_instance_new(wasm_store_t* store,
                                   const wasm_module_t* module,
                                   const wasm_extern_t* const imports[]) {
  return release(wasm::Instance::make(
      store, module, reinterpret_cast<const wasm::Extern* const*>(imports)));
}

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

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

}  // extern "C"