// 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"