blob: 8bc82f21da147299de52f235f6998cc994be4ba2 [file] [log] [blame]
// Copyright 2015 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.
#include "src/wasm/wasm-js.h"
#include <cinttypes>
#include <cstring>
#include "src/api/api-inl.h"
#include "src/api/api-natives.h"
#include "src/ast/ast.h"
#include "src/base/logging.h"
#include "src/base/overflowing-math.h"
#include "src/common/assert-scope.h"
#include "src/execution/execution.h"
#include "src/execution/frames-inl.h"
#include "src/execution/isolate.h"
#include "src/handles/handles.h"
#include "src/heap/factory.h"
#include "src/init/v8.h"
#include "src/objects/js-collection-inl.h"
#include "src/objects/js-promise-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/templates.h"
#include "src/parsing/parse-info.h"
#include "src/tasks/task-utils.h"
#include "src/trap-handler/trap-handler.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/streaming-decoder.h"
#include "src/wasm/value-type.h"
#include "src/wasm/wasm-debug.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-serialization.h"
#include "src/wasm/wasm-value.h"
using v8::internal::wasm::ErrorThrower;
using v8::internal::wasm::ScheduledErrorThrower;
namespace v8 {
class WasmStreaming::WasmStreamingImpl {
public:
WasmStreamingImpl(
Isolate* isolate, const char* api_method_name,
std::shared_ptr<internal::wasm::CompilationResultResolver> resolver)
: isolate_(isolate), resolver_(std::move(resolver)) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
streaming_decoder_ = i_isolate->wasm_engine()->StartStreamingCompilation(
i_isolate, enabled_features, handle(i_isolate->context(), i_isolate),
api_method_name, resolver_);
}
void OnBytesReceived(const uint8_t* bytes, size_t size) {
streaming_decoder_->OnBytesReceived(i::VectorOf(bytes, size));
}
void Finish() { streaming_decoder_->Finish(); }
void Abort(MaybeLocal<Value> exception) {
i::HandleScope scope(reinterpret_cast<i::Isolate*>(isolate_));
streaming_decoder_->Abort();
// If no exception value is provided, we do not reject the promise. This can
// happen when streaming compilation gets aborted when no script execution
// is allowed anymore, e.g. when a browser tab gets refreshed.
if (exception.IsEmpty()) return;
resolver_->OnCompilationFailed(
Utils::OpenHandle(*exception.ToLocalChecked()));
}
bool SetCompiledModuleBytes(const uint8_t* bytes, size_t size) {
if (!i::wasm::IsSupportedVersion({bytes, size})) return false;
return streaming_decoder_->SetCompiledModuleBytes({bytes, size});
}
void SetClient(std::shared_ptr<Client> client) {
streaming_decoder_->SetModuleCompiledCallback(
[client, streaming_decoder = streaming_decoder_](
const std::shared_ptr<i::wasm::NativeModule>& native_module) {
i::Vector<const char> url = streaming_decoder->url();
auto compiled_wasm_module =
CompiledWasmModule(native_module, url.begin(), url.size());
client->OnModuleCompiled(compiled_wasm_module);
});
}
void SetUrl(internal::Vector<const char> url) {
streaming_decoder_->SetUrl(url);
}
private:
Isolate* const isolate_;
std::shared_ptr<internal::wasm::StreamingDecoder> streaming_decoder_;
std::shared_ptr<internal::wasm::CompilationResultResolver> resolver_;
};
WasmStreaming::WasmStreaming(std::unique_ptr<WasmStreamingImpl> impl)
: impl_(std::move(impl)) {
TRACE_EVENT0("v8.wasm", "wasm.InitializeStreaming");
}
// The destructor is defined here because we have a unique_ptr with forward
// declaration.
WasmStreaming::~WasmStreaming() = default;
void WasmStreaming::OnBytesReceived(const uint8_t* bytes, size_t size) {
TRACE_EVENT1("v8.wasm", "wasm.OnBytesReceived", "bytes", size);
impl_->OnBytesReceived(bytes, size);
}
void WasmStreaming::Finish() {
TRACE_EVENT0("v8.wasm", "wasm.FinishStreaming");
impl_->Finish();
}
void WasmStreaming::Abort(MaybeLocal<Value> exception) {
TRACE_EVENT0("v8.wasm", "wasm.AbortStreaming");
impl_->Abort(exception);
}
bool WasmStreaming::SetCompiledModuleBytes(const uint8_t* bytes, size_t size) {
TRACE_EVENT0("v8.wasm", "wasm.SetCompiledModuleBytes");
return impl_->SetCompiledModuleBytes(bytes, size);
}
void WasmStreaming::SetClient(std::shared_ptr<Client> client) {
TRACE_EVENT0("v8.wasm", "wasm.WasmStreaming.SetClient");
impl_->SetClient(client);
}
void WasmStreaming::SetUrl(const char* url, size_t length) {
TRACE_EVENT0("v8.wasm", "wasm.SetUrl");
impl_->SetUrl(internal::VectorOf(url, length));
}
// static
std::shared_ptr<WasmStreaming> WasmStreaming::Unpack(Isolate* isolate,
Local<Value> value) {
TRACE_EVENT0("v8.wasm", "wasm.WasmStreaming.Unpack");
i::HandleScope scope(reinterpret_cast<i::Isolate*>(isolate));
auto managed =
i::Handle<i::Managed<WasmStreaming>>::cast(Utils::OpenHandle(*value));
return managed->get();
}
namespace {
#define ASSIGN(type, var, expr) \
Local<type> var; \
do { \
if (!expr.ToLocal(&var)) { \
DCHECK(i_isolate->has_scheduled_exception()); \
return; \
} else { \
DCHECK(!i_isolate->has_scheduled_exception()); \
} \
} while (false)
i::Handle<i::String> v8_str(i::Isolate* isolate, const char* str) {
return isolate->factory()->NewStringFromAsciiChecked(str);
}
Local<String> v8_str(Isolate* isolate, const char* str) {
return Utils::ToLocal(v8_str(reinterpret_cast<i::Isolate*>(isolate), str));
}
#define GET_FIRST_ARGUMENT_AS(Type) \
i::MaybeHandle<i::Wasm##Type##Object> GetFirstArgumentAs##Type( \
const v8::FunctionCallbackInfo<v8::Value>& args, \
ErrorThrower* thrower) { \
i::Handle<i::Object> arg0 = Utils::OpenHandle(*args[0]); \
if (!arg0->IsWasm##Type##Object()) { \
thrower->TypeError("Argument 0 must be a WebAssembly." #Type); \
return {}; \
} \
Local<Object> obj = Local<Object>::Cast(args[0]); \
return i::Handle<i::Wasm##Type##Object>::cast( \
v8::Utils::OpenHandle(*obj)); \
}
GET_FIRST_ARGUMENT_AS(Module)
GET_FIRST_ARGUMENT_AS(Memory)
GET_FIRST_ARGUMENT_AS(Table)
GET_FIRST_ARGUMENT_AS(Global)
#undef GET_FIRST_ARGUMENT_AS
i::wasm::ModuleWireBytes GetFirstArgumentAsBytes(
const v8::FunctionCallbackInfo<v8::Value>& args, ErrorThrower* thrower,
bool* is_shared) {
const uint8_t* start = nullptr;
size_t length = 0;
v8::Local<v8::Value> source = args[0];
if (source->IsArrayBuffer()) {
// A raw array buffer was passed.
Local<ArrayBuffer> buffer = Local<ArrayBuffer>::Cast(source);
auto backing_store = buffer->GetBackingStore();
start = reinterpret_cast<const uint8_t*>(backing_store->Data());
length = backing_store->ByteLength();
*is_shared = buffer->IsSharedArrayBuffer();
} else if (source->IsTypedArray()) {
// A TypedArray was passed.
Local<TypedArray> array = Local<TypedArray>::Cast(source);
Local<ArrayBuffer> buffer = array->Buffer();
auto backing_store = buffer->GetBackingStore();
start = reinterpret_cast<const uint8_t*>(backing_store->Data()) +
array->ByteOffset();
length = array->ByteLength();
*is_shared = buffer->IsSharedArrayBuffer();
} else {
thrower->TypeError("Argument 0 must be a buffer source");
}
DCHECK_IMPLIES(length, start != nullptr);
if (length == 0) {
thrower->CompileError("BufferSource argument is empty");
}
size_t max_length = i::wasm::max_module_size();
if (length > max_length) {
thrower->RangeError("buffer source exceeds maximum size of %zu (is %zu)",
max_length, length);
}
if (thrower->error()) return i::wasm::ModuleWireBytes(nullptr, nullptr);
return i::wasm::ModuleWireBytes(start, start + length);
}
i::MaybeHandle<i::JSReceiver> GetValueAsImports(Local<Value> arg,
ErrorThrower* thrower) {
if (arg->IsUndefined()) return {};
if (!arg->IsObject()) {
thrower->TypeError("Argument 1 must be an object");
return {};
}
Local<Object> obj = Local<Object>::Cast(arg);
return i::Handle<i::JSReceiver>::cast(v8::Utils::OpenHandle(*obj));
}
namespace {
// This class resolves the result of WebAssembly.compile. It just places the
// compilation result in the supplied {promise}.
class AsyncCompilationResolver : public i::wasm::CompilationResultResolver {
public:
AsyncCompilationResolver(i::Isolate* isolate, i::Handle<i::JSPromise> promise)
: promise_(isolate->global_handles()->Create(*promise)) {
i::GlobalHandles::AnnotateStrongRetainer(promise_.location(),
kGlobalPromiseHandle);
}
~AsyncCompilationResolver() override {
i::GlobalHandles::Destroy(promise_.location());
}
void OnCompilationSucceeded(i::Handle<i::WasmModuleObject> result) override {
if (finished_) return;
finished_ = true;
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Resolve(promise_, result);
CHECK_EQ(promise_result.is_null(),
promise_->GetIsolate()->has_pending_exception());
}
void OnCompilationFailed(i::Handle<i::Object> error_reason) override {
if (finished_) return;
finished_ = true;
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Reject(promise_, error_reason);
CHECK_EQ(promise_result.is_null(),
promise_->GetIsolate()->has_pending_exception());
}
private:
static constexpr char kGlobalPromiseHandle[] =
"AsyncCompilationResolver::promise_";
bool finished_ = false;
i::Handle<i::JSPromise> promise_;
};
constexpr char AsyncCompilationResolver::kGlobalPromiseHandle[];
// This class resolves the result of WebAssembly.instantiate(module, imports).
// It just places the instantiation result in the supplied {promise}.
class InstantiateModuleResultResolver
: public i::wasm::InstantiationResultResolver {
public:
InstantiateModuleResultResolver(i::Isolate* isolate,
i::Handle<i::JSPromise> promise)
: promise_(isolate->global_handles()->Create(*promise)) {
i::GlobalHandles::AnnotateStrongRetainer(promise_.location(),
kGlobalPromiseHandle);
}
~InstantiateModuleResultResolver() override {
i::GlobalHandles::Destroy(promise_.location());
}
void OnInstantiationSucceeded(
i::Handle<i::WasmInstanceObject> instance) override {
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Resolve(promise_, instance);
CHECK_EQ(promise_result.is_null(),
promise_->GetIsolate()->has_pending_exception());
}
void OnInstantiationFailed(i::Handle<i::Object> error_reason) override {
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Reject(promise_, error_reason);
CHECK_EQ(promise_result.is_null(),
promise_->GetIsolate()->has_pending_exception());
}
private:
static constexpr char kGlobalPromiseHandle[] =
"InstantiateModuleResultResolver::promise_";
i::Handle<i::JSPromise> promise_;
};
constexpr char InstantiateModuleResultResolver::kGlobalPromiseHandle[];
// This class resolves the result of WebAssembly.instantiate(bytes, imports).
// For that it creates a new {JSObject} which contains both the provided
// {WasmModuleObject} and the resulting {WebAssemblyInstanceObject} itself.
class InstantiateBytesResultResolver
: public i::wasm::InstantiationResultResolver {
public:
InstantiateBytesResultResolver(i::Isolate* isolate,
i::Handle<i::JSPromise> promise,
i::Handle<i::WasmModuleObject> module)
: isolate_(isolate),
promise_(isolate_->global_handles()->Create(*promise)),
module_(isolate_->global_handles()->Create(*module)) {
i::GlobalHandles::AnnotateStrongRetainer(promise_.location(),
kGlobalPromiseHandle);
i::GlobalHandles::AnnotateStrongRetainer(module_.location(),
kGlobalModuleHandle);
}
~InstantiateBytesResultResolver() override {
i::GlobalHandles::Destroy(promise_.location());
i::GlobalHandles::Destroy(module_.location());
}
void OnInstantiationSucceeded(
i::Handle<i::WasmInstanceObject> instance) override {
// The result is a JSObject with 2 fields which contain the
// WasmInstanceObject and the WasmModuleObject.
i::Handle<i::JSObject> result =
isolate_->factory()->NewJSObject(isolate_->object_function());
i::Handle<i::String> instance_name =
isolate_->factory()->NewStringFromStaticChars("instance");
i::Handle<i::String> module_name =
isolate_->factory()->NewStringFromStaticChars("module");
i::JSObject::AddProperty(isolate_, result, instance_name, instance,
i::NONE);
i::JSObject::AddProperty(isolate_, result, module_name, module_, i::NONE);
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Resolve(promise_, result);
CHECK_EQ(promise_result.is_null(), isolate_->has_pending_exception());
}
void OnInstantiationFailed(i::Handle<i::Object> error_reason) override {
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Reject(promise_, error_reason);
CHECK_EQ(promise_result.is_null(), isolate_->has_pending_exception());
}
private:
static constexpr char kGlobalPromiseHandle[] =
"InstantiateBytesResultResolver::promise_";
static constexpr char kGlobalModuleHandle[] =
"InstantiateBytesResultResolver::module_";
i::Isolate* isolate_;
i::Handle<i::JSPromise> promise_;
i::Handle<i::WasmModuleObject> module_;
};
constexpr char InstantiateBytesResultResolver::kGlobalPromiseHandle[];
constexpr char InstantiateBytesResultResolver::kGlobalModuleHandle[];
// This class is the {CompilationResultResolver} for
// WebAssembly.instantiate(bytes, imports). When compilation finishes,
// {AsyncInstantiate} is started on the compilation result.
class AsyncInstantiateCompileResultResolver
: public i::wasm::CompilationResultResolver {
public:
AsyncInstantiateCompileResultResolver(
i::Isolate* isolate, i::Handle<i::JSPromise> promise,
i::MaybeHandle<i::JSReceiver> maybe_imports)
: isolate_(isolate),
promise_(isolate_->global_handles()->Create(*promise)),
maybe_imports_(maybe_imports.is_null()
? maybe_imports
: isolate_->global_handles()->Create(
*maybe_imports.ToHandleChecked())) {
i::GlobalHandles::AnnotateStrongRetainer(promise_.location(),
kGlobalPromiseHandle);
if (!maybe_imports_.is_null()) {
i::GlobalHandles::AnnotateStrongRetainer(
maybe_imports_.ToHandleChecked().location(), kGlobalImportsHandle);
}
}
~AsyncInstantiateCompileResultResolver() override {
i::GlobalHandles::Destroy(promise_.location());
if (!maybe_imports_.is_null()) {
i::GlobalHandles::Destroy(maybe_imports_.ToHandleChecked().location());
}
}
void OnCompilationSucceeded(i::Handle<i::WasmModuleObject> result) override {
if (finished_) return;
finished_ = true;
isolate_->wasm_engine()->AsyncInstantiate(
isolate_,
std::make_unique<InstantiateBytesResultResolver>(isolate_, promise_,
result),
result, maybe_imports_);
}
void OnCompilationFailed(i::Handle<i::Object> error_reason) override {
if (finished_) return;
finished_ = true;
i::MaybeHandle<i::Object> promise_result =
i::JSPromise::Reject(promise_, error_reason);
CHECK_EQ(promise_result.is_null(), isolate_->has_pending_exception());
}
private:
static constexpr char kGlobalPromiseHandle[] =
"AsyncInstantiateCompileResultResolver::promise_";
static constexpr char kGlobalImportsHandle[] =
"AsyncInstantiateCompileResultResolver::module_";
bool finished_ = false;
i::Isolate* isolate_;
i::Handle<i::JSPromise> promise_;
i::MaybeHandle<i::JSReceiver> maybe_imports_;
};
constexpr char AsyncInstantiateCompileResultResolver::kGlobalPromiseHandle[];
constexpr char AsyncInstantiateCompileResultResolver::kGlobalImportsHandle[];
std::string ToString(const char* name) { return std::string(name); }
std::string ToString(const i::Handle<i::String> name) {
return std::string("Property '") + name->ToCString().get() + "'";
}
// Web IDL: '[EnforceRange] unsigned long'
// Previously called ToNonWrappingUint32 in the draft WebAssembly JS spec.
// https://heycam.github.io/webidl/#EnforceRange
template <typename T>
bool EnforceUint32(T argument_name, Local<v8::Value> v, Local<Context> context,
ErrorThrower* thrower, uint32_t* res) {
double double_number;
if (!v->NumberValue(context).To(&double_number)) {
thrower->TypeError("%s must be convertible to a number",
ToString(argument_name).c_str());
return false;
}
if (!std::isfinite(double_number)) {
thrower->TypeError("%s must be convertible to a valid number",
ToString(argument_name).c_str());
return false;
}
if (double_number < 0) {
thrower->TypeError("%s must be non-negative",
ToString(argument_name).c_str());
return false;
}
if (double_number > std::numeric_limits<uint32_t>::max()) {
thrower->TypeError("%s must be in the unsigned long range",
ToString(argument_name).c_str());
return false;
}
*res = static_cast<uint32_t>(double_number);
return true;
}
} // namespace
// WebAssembly.compile(bytes) -> Promise
void WebAssemblyCompile(const v8::FunctionCallbackInfo<v8::Value>& args) {
constexpr const char* kAPIMethodName = "WebAssembly.compile()";
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, kAPIMethodName);
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, i_isolate->native_context())) {
thrower.CompileError("Wasm code generation disallowed by embedder");
}
Local<Context> context = isolate->GetCurrentContext();
ASSIGN(Promise::Resolver, promise_resolver, Promise::Resolver::New(context));
Local<Promise> promise = promise_resolver->GetPromise();
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(promise);
std::shared_ptr<i::wasm::CompilationResultResolver> resolver(
new AsyncCompilationResolver(i_isolate, Utils::OpenHandle(*promise)));
bool is_shared = false;
auto bytes = GetFirstArgumentAsBytes(args, &thrower, &is_shared);
if (thrower.error()) {
resolver->OnCompilationFailed(thrower.Reify());
return;
}
// Asynchronous compilation handles copying wire bytes if necessary.
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
i_isolate->wasm_engine()->AsyncCompile(i_isolate, enabled_features,
std::move(resolver), bytes, is_shared,
kAPIMethodName);
}
void WasmStreamingCallbackForTesting(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.compile()");
std::shared_ptr<v8::WasmStreaming> streaming =
v8::WasmStreaming::Unpack(args.GetIsolate(), args.Data());
bool is_shared = false;
i::wasm::ModuleWireBytes bytes =
GetFirstArgumentAsBytes(args, &thrower, &is_shared);
if (thrower.error()) {
streaming->Abort(Utils::ToLocal(thrower.Reify()));
return;
}
streaming->OnBytesReceived(bytes.start(), bytes.length());
streaming->Finish();
CHECK(!thrower.error());
}
void WasmStreamingPromiseFailedCallback(
const v8::FunctionCallbackInfo<v8::Value>& args) {
std::shared_ptr<v8::WasmStreaming> streaming =
v8::WasmStreaming::Unpack(args.GetIsolate(), args.Data());
streaming->Abort(args[0]);
}
// WebAssembly.compileStreaming(Response | Promise<Response>)
// -> Promise<WebAssembly.Module>
void WebAssemblyCompileStreaming(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
const char* const kAPIMethodName = "WebAssembly.compileStreaming()";
ScheduledErrorThrower thrower(i_isolate, kAPIMethodName);
Local<Context> context = isolate->GetCurrentContext();
// Create and assign the return value of this function.
ASSIGN(Promise::Resolver, result_resolver, Promise::Resolver::New(context));
Local<Promise> promise = result_resolver->GetPromise();
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(promise);
// Prepare the CompilationResultResolver for the compilation.
auto resolver = std::make_shared<AsyncCompilationResolver>(
i_isolate, Utils::OpenHandle(*promise));
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, i_isolate->native_context())) {
thrower.CompileError("Wasm code generation disallowed by embedder");
resolver->OnCompilationFailed(thrower.Reify());
return;
}
// Allocate the streaming decoder in a Managed so we can pass it to the
// embedder.
i::Handle<i::Managed<WasmStreaming>> data =
i::Managed<WasmStreaming>::Allocate(
i_isolate, 0,
std::make_unique<WasmStreaming::WasmStreamingImpl>(
isolate, kAPIMethodName, resolver));
DCHECK_NOT_NULL(i_isolate->wasm_streaming_callback());
ASSIGN(
v8::Function, compile_callback,
v8::Function::New(context, i_isolate->wasm_streaming_callback(),
Utils::ToLocal(i::Handle<i::Object>::cast(data)), 1));
ASSIGN(
v8::Function, reject_callback,
v8::Function::New(context, WasmStreamingPromiseFailedCallback,
Utils::ToLocal(i::Handle<i::Object>::cast(data)), 1));
// The parameter may be of type {Response} or of type {Promise<Response>}.
// Treat either case of parameter as Promise.resolve(parameter)
// as per https://www.w3.org/2001/tag/doc/promises-guide#resolve-arguments
// Ending with:
// return Promise.resolve(parameter).then(compile_callback);
ASSIGN(Promise::Resolver, input_resolver, Promise::Resolver::New(context));
if (!input_resolver->Resolve(context, args[0]).IsJust()) return;
// We do not have any use of the result here. The {compile_callback} will
// start streaming compilation, which will eventually resolve the promise we
// set as result value.
USE(input_resolver->GetPromise()->Then(context, compile_callback,
reject_callback));
}
// WebAssembly.validate(bytes) -> bool
void WebAssemblyValidate(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.validate()");
bool is_shared = false;
auto bytes = GetFirstArgumentAsBytes(args, &thrower, &is_shared);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
if (thrower.error()) {
if (thrower.wasm_error()) thrower.Reset(); // Clear error.
return_value.Set(v8::False(isolate));
return;
}
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
bool validated = false;
if (is_shared) {
// Make a copy of the wire bytes to avoid concurrent modification.
std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]);
memcpy(copy.get(), bytes.start(), bytes.length());
i::wasm::ModuleWireBytes bytes_copy(copy.get(),
copy.get() + bytes.length());
validated = i_isolate->wasm_engine()->SyncValidate(
i_isolate, enabled_features, bytes_copy);
} else {
// The wire bytes are not shared, OK to use them directly.
validated = i_isolate->wasm_engine()->SyncValidate(i_isolate,
enabled_features, bytes);
}
return_value.Set(Boolean::New(isolate, validated));
}
// new WebAssembly.Module(bytes) -> WebAssembly.Module
void WebAssemblyModule(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
if (i_isolate->wasm_module_callback()(args)) return;
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Module()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Module must be invoked with 'new'");
return;
}
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, i_isolate->native_context())) {
thrower.CompileError("Wasm code generation disallowed by embedder");
return;
}
bool is_shared = false;
auto bytes = GetFirstArgumentAsBytes(args, &thrower, &is_shared);
if (thrower.error()) {
return;
}
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
i::MaybeHandle<i::Object> module_obj;
if (is_shared) {
// Make a copy of the wire bytes to avoid concurrent modification.
std::unique_ptr<uint8_t[]> copy(new uint8_t[bytes.length()]);
memcpy(copy.get(), bytes.start(), bytes.length());
i::wasm::ModuleWireBytes bytes_copy(copy.get(),
copy.get() + bytes.length());
module_obj = i_isolate->wasm_engine()->SyncCompile(
i_isolate, enabled_features, &thrower, bytes_copy);
} else {
// The wire bytes are not shared, OK to use them directly.
module_obj = i_isolate->wasm_engine()->SyncCompile(
i_isolate, enabled_features, &thrower, bytes);
}
if (module_obj.is_null()) return;
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(module_obj.ToHandleChecked()));
}
// WebAssembly.Module.imports(module) -> Array<Import>
void WebAssemblyModuleImports(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Module.imports()");
auto maybe_module = GetFirstArgumentAsModule(args, &thrower);
if (thrower.error()) return;
auto imports = i::wasm::GetImports(i_isolate, maybe_module.ToHandleChecked());
args.GetReturnValue().Set(Utils::ToLocal(imports));
}
// WebAssembly.Module.exports(module) -> Array<Export>
void WebAssemblyModuleExports(const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Module.exports()");
auto maybe_module = GetFirstArgumentAsModule(args, &thrower);
if (thrower.error()) return;
auto exports = i::wasm::GetExports(i_isolate, maybe_module.ToHandleChecked());
args.GetReturnValue().Set(Utils::ToLocal(exports));
}
// WebAssembly.Module.customSections(module, name) -> Array<Section>
void WebAssemblyModuleCustomSections(
const v8::FunctionCallbackInfo<v8::Value>& args) {
HandleScope scope(args.GetIsolate());
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate,
"WebAssembly.Module.customSections()");
auto maybe_module = GetFirstArgumentAsModule(args, &thrower);
if (thrower.error()) return;
if (args[1]->IsUndefined()) {
thrower.TypeError("Argument 1 is required");
return;
}
i::MaybeHandle<i::Object> maybe_name =
i::Object::ToString(i_isolate, Utils::OpenHandle(*args[1]));
i::Handle<i::Object> name;
if (!maybe_name.ToHandle(&name)) return;
auto custom_sections =
i::wasm::GetCustomSections(i_isolate, maybe_module.ToHandleChecked(),
i::Handle<i::String>::cast(name), &thrower);
if (thrower.error()) return;
args.GetReturnValue().Set(Utils::ToLocal(custom_sections));
}
MaybeLocal<Value> WebAssemblyInstantiateImpl(Isolate* isolate,
Local<Value> module,
Local<Value> ffi) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::MaybeHandle<i::Object> instance_object;
{
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Instance()");
// TODO(ahaas): These checks on the module should not be necessary here They
// are just a workaround for https://crbug.com/837417.
i::Handle<i::Object> module_obj = Utils::OpenHandle(*module);
if (!module_obj->IsWasmModuleObject()) {
thrower.TypeError("Argument 0 must be a WebAssembly.Module object");
return {};
}
i::MaybeHandle<i::JSReceiver> maybe_imports =
GetValueAsImports(ffi, &thrower);
if (thrower.error()) return {};
instance_object = i_isolate->wasm_engine()->SyncInstantiate(
i_isolate, &thrower, i::Handle<i::WasmModuleObject>::cast(module_obj),
maybe_imports, i::MaybeHandle<i::JSArrayBuffer>());
}
DCHECK_EQ(instance_object.is_null(), i_isolate->has_scheduled_exception());
if (instance_object.is_null()) return {};
return Utils::ToLocal(instance_object.ToHandleChecked());
}
// new WebAssembly.Instance(module, imports) -> WebAssembly.Instance
void WebAssemblyInstance(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
HandleScope scope(args.GetIsolate());
if (i_isolate->wasm_instance_callback()(args)) return;
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Instance()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Instance must be invoked with 'new'");
return;
}
GetFirstArgumentAsModule(args, &thrower);
if (thrower.error()) return;
// If args.Length < 2, this will be undefined - see FunctionCallbackInfo.
// We'll check for that in WebAssemblyInstantiateImpl.
Local<Value> data = args[1];
Local<Value> instance;
if (WebAssemblyInstantiateImpl(isolate, args[0], data).ToLocal(&instance)) {
args.GetReturnValue().Set(instance);
}
}
// WebAssembly.instantiateStreaming(Response | Promise<Response> [, imports])
// -> Promise<ResultObject>
// (where ResultObject has a "module" and an "instance" field)
void WebAssemblyInstantiateStreaming(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
const char* const kAPIMethodName = "WebAssembly.instantiateStreaming()";
ScheduledErrorThrower thrower(i_isolate, kAPIMethodName);
// Create and assign the return value of this function.
ASSIGN(Promise::Resolver, result_resolver, Promise::Resolver::New(context));
Local<Promise> promise = result_resolver->GetPromise();
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(promise);
// Create an InstantiateResultResolver in case there is an issue with the
// passed parameters.
std::unique_ptr<i::wasm::InstantiationResultResolver> resolver(
new InstantiateModuleResultResolver(i_isolate,
Utils::OpenHandle(*promise)));
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, i_isolate->native_context())) {
thrower.CompileError("Wasm code generation disallowed by embedder");
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
// If args.Length < 2, this will be undefined - see FunctionCallbackInfo.
Local<Value> ffi = args[1];
i::MaybeHandle<i::JSReceiver> maybe_imports =
GetValueAsImports(ffi, &thrower);
if (thrower.error()) {
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
// We start compilation now, we have no use for the
// {InstantiationResultResolver}.
resolver.reset();
std::shared_ptr<i::wasm::CompilationResultResolver> compilation_resolver(
new AsyncInstantiateCompileResultResolver(
i_isolate, Utils::OpenHandle(*promise), maybe_imports));
// Allocate the streaming decoder in a Managed so we can pass it to the
// embedder.
i::Handle<i::Managed<WasmStreaming>> data =
i::Managed<WasmStreaming>::Allocate(
i_isolate, 0,
std::make_unique<WasmStreaming::WasmStreamingImpl>(
isolate, kAPIMethodName, compilation_resolver));
DCHECK_NOT_NULL(i_isolate->wasm_streaming_callback());
ASSIGN(
v8::Function, compile_callback,
v8::Function::New(context, i_isolate->wasm_streaming_callback(),
Utils::ToLocal(i::Handle<i::Object>::cast(data)), 1));
ASSIGN(
v8::Function, reject_callback,
v8::Function::New(context, WasmStreamingPromiseFailedCallback,
Utils::ToLocal(i::Handle<i::Object>::cast(data)), 1));
// The parameter may be of type {Response} or of type {Promise<Response>}.
// Treat either case of parameter as Promise.resolve(parameter)
// as per https://www.w3.org/2001/tag/doc/promises-guide#resolve-arguments
// Ending with:
// return Promise.resolve(parameter).then(compile_callback);
ASSIGN(Promise::Resolver, input_resolver, Promise::Resolver::New(context));
if (!input_resolver->Resolve(context, args[0]).IsJust()) return;
// We do not have any use of the result here. The {compile_callback} will
// start streaming compilation, which will eventually resolve the promise we
// set as result value.
USE(input_resolver->GetPromise()->Then(context, compile_callback,
reject_callback));
}
// WebAssembly.instantiate(module, imports) -> WebAssembly.Instance
// WebAssembly.instantiate(bytes, imports) ->
// {module: WebAssembly.Module, instance: WebAssembly.Instance}
void WebAssemblyInstantiate(const v8::FunctionCallbackInfo<v8::Value>& args) {
constexpr const char* kAPIMethodName = "WebAssembly.instantiate()";
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
ScheduledErrorThrower thrower(i_isolate, kAPIMethodName);
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
ASSIGN(Promise::Resolver, promise_resolver, Promise::Resolver::New(context));
Local<Promise> promise = promise_resolver->GetPromise();
args.GetReturnValue().Set(promise);
std::unique_ptr<i::wasm::InstantiationResultResolver> resolver(
new InstantiateModuleResultResolver(i_isolate,
Utils::OpenHandle(*promise)));
Local<Value> first_arg_value = args[0];
i::Handle<i::Object> first_arg = Utils::OpenHandle(*first_arg_value);
if (!first_arg->IsJSObject()) {
thrower.TypeError(
"Argument 0 must be a buffer source or a WebAssembly.Module object");
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
// If args.Length < 2, this will be undefined - see FunctionCallbackInfo.
Local<Value> ffi = args[1];
i::MaybeHandle<i::JSReceiver> maybe_imports =
GetValueAsImports(ffi, &thrower);
if (thrower.error()) {
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
if (first_arg->IsWasmModuleObject()) {
i::Handle<i::WasmModuleObject> module_obj =
i::Handle<i::WasmModuleObject>::cast(first_arg);
i_isolate->wasm_engine()->AsyncInstantiate(i_isolate, std::move(resolver),
module_obj, maybe_imports);
return;
}
bool is_shared = false;
auto bytes = GetFirstArgumentAsBytes(args, &thrower, &is_shared);
if (thrower.error()) {
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
// We start compilation now, we have no use for the
// {InstantiationResultResolver}.
resolver.reset();
std::shared_ptr<i::wasm::CompilationResultResolver> compilation_resolver(
new AsyncInstantiateCompileResultResolver(
i_isolate, Utils::OpenHandle(*promise), maybe_imports));
// The first parameter is a buffer source, we have to check if we are allowed
// to compile it.
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, i_isolate->native_context())) {
thrower.CompileError("Wasm code generation disallowed by embedder");
compilation_resolver->OnCompilationFailed(thrower.Reify());
return;
}
// Asynchronous compilation handles copying wire bytes if necessary.
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
i_isolate->wasm_engine()->AsyncCompile(i_isolate, enabled_features,
std::move(compilation_resolver), bytes,
is_shared, kAPIMethodName);
}
bool GetIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower,
Local<Context> context, v8::Local<v8::Value> value,
i::Handle<i::String> property_name, int64_t* result,
int64_t lower_bound, uint64_t upper_bound) {
uint32_t number;
if (!EnforceUint32(property_name, value, context, thrower, &number)) {
return false;
}
if (number < lower_bound) {
thrower->RangeError("Property '%s': value %" PRIu32
" is below the lower bound %" PRIx64,
property_name->ToCString().get(), number, lower_bound);
return false;
}
if (number > upper_bound) {
thrower->RangeError("Property '%s': value %" PRIu32
" is above the upper bound %" PRIu64,
property_name->ToCString().get(), number, upper_bound);
return false;
}
*result = static_cast<int64_t>(number);
return true;
}
bool GetOptionalIntegerProperty(v8::Isolate* isolate, ErrorThrower* thrower,
Local<Context> context,
Local<v8::Object> object,
Local<String> property, bool* has_property,
int64_t* result, int64_t lower_bound,
uint64_t upper_bound) {
v8::Local<v8::Value> value;
if (!object->Get(context, property).ToLocal(&value)) {
return false;
}
// Web IDL: dictionary presence
// https://heycam.github.io/webidl/#dfn-present
if (value->IsUndefined()) {
if (has_property != nullptr) *has_property = false;
return true;
}
if (has_property != nullptr) *has_property = true;
i::Handle<i::String> property_name = v8::Utils::OpenHandle(*property);
return GetIntegerProperty(isolate, thrower, context, value, property_name,
result, lower_bound, upper_bound);
}
// Fetch 'initial' or 'minimum' property from object. If both are provided,
// 'initial' is used.
// TODO(aseemgarg): change behavior when the following bug is resolved:
// https://github.com/WebAssembly/js-types/issues/6
bool GetInitialOrMinimumProperty(v8::Isolate* isolate, ErrorThrower* thrower,
Local<Context> context,
Local<v8::Object> object, int64_t* result,
int64_t lower_bound, uint64_t upper_bound) {
bool has_initial = false;
if (!GetOptionalIntegerProperty(isolate, thrower, context, object,
v8_str(isolate, "initial"), &has_initial,
result, lower_bound, upper_bound)) {
return false;
}
auto enabled_features = i::wasm::WasmFeatures::FromFlags();
if (!has_initial && enabled_features.has_type_reflection()) {
if (!GetOptionalIntegerProperty(isolate, thrower, context, object,
v8_str(isolate, "minimum"), &has_initial,
result, lower_bound, upper_bound)) {
return false;
}
}
if (!has_initial) {
// TODO(aseemgarg): update error message when the spec issue is resolved.
thrower->TypeError("Property 'initial' is required");
return false;
}
return true;
}
// new WebAssembly.Table(args) -> WebAssembly.Table
void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Module()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Table must be invoked with 'new'");
return;
}
if (!args[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a table descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
i::wasm::ValueType type;
// The descriptor's 'element'.
{
v8::MaybeLocal<v8::Value> maybe =
descriptor->Get(context, v8_str(isolate, "element"));
v8::Local<v8::Value> value;
if (!maybe.ToLocal(&value)) return;
v8::Local<v8::String> string;
if (!value->ToString(context).ToLocal(&string)) return;
auto enabled_features = i::wasm::WasmFeatures::FromFlags();
// The JS api uses 'anyfunc' instead of 'funcref'.
if (string->StringEquals(v8_str(isolate, "anyfunc"))) {
type = i::wasm::kWasmFuncRef;
} else if (enabled_features.has_reftypes() &&
string->StringEquals(v8_str(isolate, "externref"))) {
type = i::wasm::kWasmExternRef;
} else {
thrower.TypeError(
"Descriptor property 'element' must be a WebAssembly reference type");
return;
}
}
int64_t initial = 0;
if (!GetInitialOrMinimumProperty(isolate, &thrower, context, descriptor,
&initial, 0,
i::wasm::max_table_init_entries())) {
return;
}
// The descriptor's 'maximum'.
int64_t maximum = -1;
bool has_maximum = true;
if (!GetOptionalIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "maximum"), &has_maximum,
&maximum, initial,
std::numeric_limits<uint32_t>::max())) {
return;
}
i::Handle<i::FixedArray> fixed_array;
i::Handle<i::JSObject> table_obj =
i::WasmTableObject::New(i_isolate, i::Handle<i::WasmInstanceObject>(),
type, static_cast<uint32_t>(initial), has_maximum,
static_cast<uint32_t>(maximum), &fixed_array);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(table_obj));
}
void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Memory()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Memory must be invoked with 'new'");
return;
}
if (!args[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a memory descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
int64_t initial = 0;
if (!GetInitialOrMinimumProperty(isolate, &thrower, context, descriptor,
&initial, 0, i::wasm::max_mem_pages())) {
return;
}
// The descriptor's 'maximum'.
int64_t maximum = -1;
if (!GetOptionalIntegerProperty(isolate, &thrower, context, descriptor,
v8_str(isolate, "maximum"), nullptr, &maximum,
initial, i::wasm::max_mem_pages())) {
return;
}
auto shared = i::SharedFlag::kNotShared;
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
if (enabled_features.has_threads()) {
// Shared property of descriptor
Local<String> shared_key = v8_str(isolate, "shared");
v8::MaybeLocal<v8::Value> maybe_value =
descriptor->Get(context, shared_key);
v8::Local<v8::Value> value;
if (maybe_value.ToLocal(&value)) {
shared = value->BooleanValue(isolate) ? i::SharedFlag::kShared
: i::SharedFlag::kNotShared;
} else {
DCHECK(i_isolate->has_scheduled_exception());
return;
}
// Throw TypeError if shared is true, and the descriptor has no "maximum"
if (shared == i::SharedFlag::kShared && maximum == -1) {
thrower.TypeError(
"If shared is true, maximum property should be defined.");
return;
}
}
i::Handle<i::JSObject> memory_obj;
if (!i::WasmMemoryObject::New(i_isolate, static_cast<uint32_t>(initial),
static_cast<uint32_t>(maximum), shared)
.ToHandle(&memory_obj)) {
thrower.RangeError("could not allocate memory");
return;
}
if (shared == i::SharedFlag::kShared) {
i::Handle<i::JSArrayBuffer> buffer(
i::Handle<i::WasmMemoryObject>::cast(memory_obj)->array_buffer(),
i_isolate);
Maybe<bool> result =
buffer->SetIntegrityLevel(buffer, i::FROZEN, i::kDontThrow);
if (!result.FromJust()) {
thrower.TypeError(
"Status of setting SetIntegrityLevel of buffer is false.");
return;
}
}
args.GetReturnValue().Set(Utils::ToLocal(memory_obj));
}
// Determines the type encoded in a value type property (e.g. type reflection).
// Returns false if there was an exception, true upon success. On success the
// outgoing {type} is set accordingly, or set to {wasm::kWasmStmt} in case the
// type could not be properly recognized.
bool GetValueType(Isolate* isolate, MaybeLocal<Value> maybe,
Local<Context> context, i::wasm::ValueType* type,
i::wasm::WasmFeatures enabled_features) {
v8::Local<v8::Value> value;
if (!maybe.ToLocal(&value)) return false;
v8::Local<v8::String> string;
if (!value->ToString(context).ToLocal(&string)) return false;
if (string->StringEquals(v8_str(isolate, "i32"))) {
*type = i::wasm::kWasmI32;
} else if (string->StringEquals(v8_str(isolate, "f32"))) {
*type = i::wasm::kWasmF32;
} else if (string->StringEquals(v8_str(isolate, "i64"))) {
*type = i::wasm::kWasmI64;
} else if (string->StringEquals(v8_str(isolate, "f64"))) {
*type = i::wasm::kWasmF64;
} else if (enabled_features.has_reftypes() &&
string->StringEquals(v8_str(isolate, "externref"))) {
*type = i::wasm::kWasmExternRef;
} else if (enabled_features.has_reftypes() &&
string->StringEquals(v8_str(isolate, "anyfunc"))) {
// The JS api spec uses 'anyfunc' instead of 'funcref'.
*type = i::wasm::kWasmFuncRef;
} else if (enabled_features.has_eh() &&
string->StringEquals(v8_str(isolate, "exnref"))) {
*type = i::wasm::kWasmExnRef;
} else if (enabled_features.has_gc() &&
string->StringEquals(v8_str(isolate, "eqref"))) {
*type = i::wasm::kWasmEqRef;
} else {
// Unrecognized type.
*type = i::wasm::kWasmStmt;
}
return true;
}
// WebAssembly.Global
void WebAssemblyGlobal(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Global()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Global must be invoked with 'new'");
return;
}
if (!args[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a global descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(args[0]);
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
// The descriptor's 'mutable'.
bool is_mutable = false;
{
Local<String> mutable_key = v8_str(isolate, "mutable");
v8::MaybeLocal<v8::Value> maybe = descriptor->Get(context, mutable_key);
v8::Local<v8::Value> value;
if (maybe.ToLocal(&value)) {
is_mutable = value->BooleanValue(isolate);
} else {
DCHECK(i_isolate->has_scheduled_exception());
return;
}
}
// The descriptor's type, called 'value'. It is called 'value' because this
// descriptor is planned to be re-used as the global's type for reflection,
// so calling it 'type' is redundant.
i::wasm::ValueType type;
{
v8::MaybeLocal<v8::Value> maybe =
descriptor->Get(context, v8_str(isolate, "value"));
if (!GetValueType(isolate, maybe, context, &type, enabled_features)) return;
if (type == i::wasm::kWasmStmt) {
thrower.TypeError(
"Descriptor property 'value' must be a WebAssembly type");
return;
}
}
const uint32_t offset = 0;
i::MaybeHandle<i::WasmGlobalObject> maybe_global_obj =
i::WasmGlobalObject::New(i_isolate, i::Handle<i::WasmInstanceObject>(),
i::MaybeHandle<i::JSArrayBuffer>(),
i::MaybeHandle<i::FixedArray>(), type, offset,
is_mutable);
i::Handle<i::WasmGlobalObject> global_obj;
if (!maybe_global_obj.ToHandle(&global_obj)) {
thrower.RangeError("could not allocate memory");
return;
}
// Convert value to a WebAssembly value, the default value is 0.
Local<v8::Value> value = Local<Value>::Cast(args[1]);
switch (type.kind()) {
case i::wasm::ValueType::kI32: {
int32_t i32_value = 0;
if (!value->IsUndefined()) {
v8::Local<v8::Int32> int32_value;
if (!value->ToInt32(context).ToLocal(&int32_value)) return;
if (!int32_value->Int32Value(context).To(&i32_value)) return;
}
global_obj->SetI32(i32_value);
break;
}
case i::wasm::ValueType::kI64: {
int64_t i64_value = 0;
if (!value->IsUndefined()) {
if (!enabled_features.has_bigint()) {
thrower.TypeError("Can't set the value of i64 WebAssembly.Global");
return;
}
v8::Local<v8::BigInt> bigint_value;
if (!value->ToBigInt(context).ToLocal(&bigint_value)) return;
i64_value = bigint_value->Int64Value();
}
global_obj->SetI64(i64_value);
break;
}
case i::wasm::ValueType::kF32: {
float f32_value = 0;
if (!value->IsUndefined()) {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
f32_value = i::DoubleToFloat32(f64_value);
}
global_obj->SetF32(f32_value);
break;
}
case i::wasm::ValueType::kF64: {
double f64_value = 0;
if (!value->IsUndefined()) {
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return;
if (!number_value->NumberValue(context).To(&f64_value)) return;
}
global_obj->SetF64(f64_value);
break;
}
case i::wasm::ValueType::kRef:
case i::wasm::ValueType::kOptRef: {
switch (type.heap_representation()) {
case i::wasm::HeapType::kExtern:
case i::wasm::HeapType::kExn: {
if (args.Length() < 2) {
// When no initial value is provided, we have to use the WebAssembly
// default value 'null', and not the JS default value 'undefined'.
global_obj->SetExternRef(i_isolate->factory()->null_value());
break;
}
global_obj->SetExternRef(Utils::OpenHandle(*value));
break;
}
case i::wasm::HeapType::kFunc: {
if (args.Length() < 2) {
// When no initial value is provided, we have to use the WebAssembly
// default value 'null', and not the JS default value 'undefined'.
global_obj->SetFuncRef(i_isolate,
i_isolate->factory()->null_value());
break;
}
if (!global_obj->SetFuncRef(i_isolate, Utils::OpenHandle(*value))) {
thrower.TypeError(
"The value of funcref globals must be null or an "
"exported function");
}
break;
}
case i::wasm::HeapType::kEq:
default:
// TODO(7748): Implement these.
UNIMPLEMENTED();
}
break;
}
case i::wasm::ValueType::kRtt:
// TODO(7748): Implement.
UNIMPLEMENTED();
case i::wasm::ValueType::kI8:
case i::wasm::ValueType::kI16:
case i::wasm::ValueType::kStmt:
case i::wasm::ValueType::kS128:
case i::wasm::ValueType::kBottom:
UNREACHABLE();
}
i::Handle<i::JSObject> global_js_object(global_obj);
args.GetReturnValue().Set(Utils::ToLocal(global_js_object));
}
// WebAssembly.Exception
void WebAssemblyException(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Exception()");
thrower.TypeError("WebAssembly.Exception cannot be called");
}
namespace {
uint32_t GetIterableLength(i::Isolate* isolate, Local<Context> context,
Local<Object> iterable) {
Local<String> length = Utils::ToLocal(isolate->factory()->length_string());
MaybeLocal<Value> property = iterable->Get(context, length);
if (property.IsEmpty()) return i::kMaxUInt32;
MaybeLocal<Uint32> number = property.ToLocalChecked()->ToArrayIndex(context);
if (number.IsEmpty()) return i::kMaxUInt32;
DCHECK_NE(i::kMaxUInt32, number.ToLocalChecked()->Value());
return number.ToLocalChecked()->Value();
}
} // namespace
// WebAssembly.Function
void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Function()");
if (!args.IsConstructCall()) {
thrower.TypeError("WebAssembly.Function must be invoked with 'new'");
return;
}
if (!args[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a function type");
return;
}
Local<Object> function_type = Local<Object>::Cast(args[0]);
Local<Context> context = isolate->GetCurrentContext();
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
// Load the 'parameters' property of the function type.
Local<String> parameters_key = v8_str(isolate, "parameters");
v8::MaybeLocal<v8::Value> parameters_maybe =
function_type->Get(context, parameters_key);
v8::Local<v8::Value> parameters_value;
if (!parameters_maybe.ToLocal(&parameters_value)) return;
if (!parameters_value->IsObject()) {
thrower.TypeError("Argument 0 must be a function type with 'parameters'");
return;
}
Local<Object> parameters = parameters_value.As<Object>();
uint32_t parameters_len = GetIterableLength(i_isolate, context, parameters);
if (parameters_len == i::kMaxUInt32) {
thrower.TypeError("Argument 0 contains parameters without 'length'");
return;
}
if (parameters_len > i::wasm::kV8MaxWasmFunctionParams) {
thrower.TypeError("Argument 0 contains too many parameters");
return;
}
// Load the 'results' property of the function type.
Local<String> results_key = v8_str(isolate, "results");
v8::MaybeLocal<v8::Value> results_maybe =
function_type->Get(context, results_key);
v8::Local<v8::Value> results_value;
if (!results_maybe.ToLocal(&results_value)) return;
if (!results_value->IsObject()) {
thrower.TypeError("Argument 0 must be a function type with 'results'");
return;
}
Local<Object> results = results_value.As<Object>();
uint32_t results_len = GetIterableLength(i_isolate, context, results);
if (results_len == i::kMaxUInt32) {
thrower.TypeError("Argument 0 contains results without 'length'");
return;
}
if (results_len > (enabled_features.has_mv()
? i::wasm::kV8MaxWasmFunctionMultiReturns
: i::wasm::kV8MaxWasmFunctionReturns)) {
thrower.TypeError("Argument 0 contains too many results");
return;
}
// Decode the function type and construct a signature.
i::Zone zone(i_isolate->allocator(), ZONE_NAME);
i::wasm::FunctionSig::Builder builder(&zone, results_len, parameters_len);
for (uint32_t i = 0; i < parameters_len; ++i) {
i::wasm::ValueType type;
MaybeLocal<Value> maybe = parameters->Get(context, i);
if (!GetValueType(isolate, maybe, context, &type, enabled_features)) return;
if (type == i::wasm::kWasmStmt) {
thrower.TypeError(
"Argument 0 parameter type at index #%u must be a value type", i);
return;
}
builder.AddParam(type);
}
for (uint32_t i = 0; i < results_len; ++i) {
i::wasm::ValueType type;
MaybeLocal<Value> maybe = results->Get(context, i);
if (!GetValueType(isolate, maybe, context, &type, enabled_features)) return;
if (type == i::wasm::kWasmStmt) {
thrower.TypeError(
"Argument 0 result type at index #%u must be a value type", i);
return;
}
builder.AddReturn(type);
}
if (!args[1]->IsFunction()) {
thrower.TypeError("Argument 1 must be a function");
return;
}
const i::wasm::FunctionSig* sig = builder.Build();
i::Handle<i::JSReceiver> callable =
Utils::OpenHandle(*args[1].As<Function>());
if (i::WasmExportedFunction::IsWasmExportedFunction(*callable)) {
if (*i::Handle<i::WasmExportedFunction>::cast(callable)->sig() == *sig) {
args.GetReturnValue().Set(Utils::ToLocal(callable));
return;
}
thrower.TypeError(
"The signature of Argument 1 (a WebAssembly function) does "
"not match the signature specified in Argument 0");
return;
}
if (i::WasmJSFunction::IsWasmJSFunction(*callable)) {
if (i::Handle<i::WasmJSFunction>::cast(callable)->MatchesSignature(sig)) {
args.GetReturnValue().Set(Utils::ToLocal(callable));
return;
}
thrower.TypeError(
"The signature of Argument 1 (a WebAssembly function) does "
"not match the signature specified in Argument 0");
return;
}
i::Handle<i::JSFunction> result =
i::WasmJSFunction::New(i_isolate, sig, callable);
args.GetReturnValue().Set(Utils::ToLocal(result));
}
// WebAssembly.Function.type(WebAssembly.Function) -> FunctionType
void WebAssemblyFunctionType(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Function.type()");
const i::wasm::FunctionSig* sig;
i::Zone zone(i_isolate->allocator(), ZONE_NAME);
i::Handle<i::Object> arg0 = Utils::OpenHandle(*args[0]);
if (i::WasmExportedFunction::IsWasmExportedFunction(*arg0)) {
sig = i::Handle<i::WasmExportedFunction>::cast(arg0)->sig();
} else if (i::WasmJSFunction::IsWasmJSFunction(*arg0)) {
sig = i::Handle<i::WasmJSFunction>::cast(arg0)->GetSignature(&zone);
} else {
thrower.TypeError("Argument 0 must be a WebAssembly.Function");
return;
}
auto type = i::wasm::GetTypeForFunction(i_isolate, sig);
args.GetReturnValue().Set(Utils::ToLocal(type));
}
constexpr const char* kName_WasmGlobalObject = "WebAssembly.Global";
constexpr const char* kName_WasmMemoryObject = "WebAssembly.Memory";
constexpr const char* kName_WasmInstanceObject = "WebAssembly.Instance";
constexpr const char* kName_WasmTableObject = "WebAssembly.Table";
#define EXTRACT_THIS(var, WasmType) \
i::Handle<i::WasmType> var; \
{ \
i::Handle<i::Object> this_arg = Utils::OpenHandle(*args.This()); \
if (!this_arg->Is##WasmType()) { \
thrower.TypeError("Receiver is not a %s", kName_##WasmType); \
return; \
} \
var = i::Handle<i::WasmType>::cast(this_arg); \
}
void WebAssemblyInstanceGetExports(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Instance.exports()");
EXTRACT_THIS(receiver, WasmInstanceObject);
i::Handle<i::JSObject> exports_object(receiver->exports_object(), i_isolate);
args.GetReturnValue().Set(Utils::ToLocal(exports_object));
}
void WebAssemblyTableGetLength(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.length()");
EXTRACT_THIS(receiver, WasmTableObject);
args.GetReturnValue().Set(
v8::Number::New(isolate, receiver->current_length()));
}
// WebAssembly.Table.grow(num, init_value = null) -> num
void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.grow()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
uint32_t grow_by;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &grow_by)) {
return;
}
i::Handle<i::Object> init_value = i_isolate->factory()->null_value();
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
if (enabled_features.has_typed_funcref()) {
if (args.Length() >= 2 && !args[1]->IsUndefined()) {
init_value = Utils::OpenHandle(*args[1]);
}
if (!i::WasmTableObject::IsValidElement(i_isolate, receiver, init_value)) {
thrower.TypeError("Argument 1 must be a valid type for the table");
return;
}
}
int old_size =
i::WasmTableObject::Grow(i_isolate, receiver, grow_by, init_value);
if (old_size < 0) {
thrower.RangeError("failed to grow table by %u", grow_by);
return;
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(old_size);
}
// WebAssembly.Table.get(num) -> JSFunction
void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.get()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
uint32_t index;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &index)) {
return;
}
if (!i::WasmTableObject::IsInBounds(i_isolate, receiver, index)) {
thrower.RangeError("invalid index %u into function table", index);
return;
}
i::Handle<i::Object> result =
i::WasmTableObject::Get(i_isolate, receiver, index);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(result));
}
// WebAssembly.Table.set(num, JSFunction)
void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.set()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(table_object, WasmTableObject);
// Parameter 0.
uint32_t index;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &index)) {
return;
}
if (!i::WasmTableObject::IsInBounds(i_isolate, table_object, index)) {
thrower.RangeError("invalid index %u into function table", index);
return;
}
i::Handle<i::Object> element = Utils::OpenHandle(*args[1]);
if (!i::WasmTableObject::IsValidElement(i_isolate, table_object, element)) {
thrower.TypeError(
"Argument 1 must be null or a WebAssembly function of type compatible "
"to 'this'");
return;
}
i::WasmTableObject::Set(i_isolate, table_object, index, element);
}
// WebAssembly.Table.type(WebAssembly.Table) -> TableType
void WebAssemblyTableType(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Table.type()");
auto maybe_table = GetFirstArgumentAsTable(args, &thrower);
if (thrower.error()) return;
i::Handle<i::WasmTableObject> table = maybe_table.ToHandleChecked();
base::Optional<uint32_t> max_size;
if (!table->maximum_length().IsUndefined()) {
uint64_t max_size64 = table->maximum_length().Number();
DCHECK_LE(max_size64, std::numeric_limits<uint32_t>::max());
max_size.emplace(static_cast<uint32_t>(max_size64));
}
auto type = i::wasm::GetTypeForTable(i_isolate, table->type(),
table->current_length(), max_size);
args.GetReturnValue().Set(Utils::ToLocal(type));
}
// WebAssembly.Memory.grow(num) -> num
void WebAssemblyMemoryGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Memory.grow()");
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmMemoryObject);
uint32_t delta_size;
if (!EnforceUint32("Argument 0", args[0], context, &thrower, &delta_size)) {
return;
}
uint64_t max_size64 = receiver->maximum_pages();
if (max_size64 > uint64_t{i::wasm::max_mem_pages()}) {
max_size64 = i::wasm::max_mem_pages();
}
i::Handle<i::JSArrayBuffer> old_buffer(receiver->array_buffer(), i_isolate);
DCHECK_LE(max_size64, std::numeric_limits<uint32_t>::max());
uint64_t old_size64 = old_buffer->byte_length() / i::wasm::kWasmPageSize;
uint64_t new_size64 = old_size64 + static_cast<uint64_t>(delta_size);
if (new_size64 > max_size64) {
thrower.RangeError("Maximum memory size exceeded");
return;
}
int32_t ret = i::WasmMemoryObject::Grow(i_isolate, receiver, delta_size);
if (ret == -1) {
thrower.RangeError("Unable to grow instance memory.");
return;
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(ret);
}
// WebAssembly.Memory.buffer -> ArrayBuffer
void WebAssemblyMemoryGetBuffer(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Memory.buffer");
EXTRACT_THIS(receiver, WasmMemoryObject);
i::Handle<i::Object> buffer_obj(receiver->array_buffer(), i_isolate);
DCHECK(buffer_obj->IsJSArrayBuffer());
i::Handle<i::JSArrayBuffer> buffer(i::JSArrayBuffer::cast(*buffer_obj),
i_isolate);
if (buffer->is_shared()) {
// TODO(gdeepti): More needed here for when cached buffer, and current
// buffer are out of sync, handle that here when bounds checks, and Grow
// are handled correctly.
Maybe<bool> result =
buffer->SetIntegrityLevel(buffer, i::FROZEN, i::kDontThrow);
if (!result.FromJust()) {
thrower.TypeError(
"Status of setting SetIntegrityLevel of buffer is false.");
}
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(buffer));
}
// WebAssembly.Memory.type(WebAssembly.Memory) -> MemoryType
void WebAssemblyMemoryType(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Memory.type()");
auto maybe_memory = GetFirstArgumentAsMemory(args, &thrower);
if (thrower.error()) return;
i::Handle<i::WasmMemoryObject> memory = maybe_memory.ToHandleChecked();
i::Handle<i::JSArrayBuffer> buffer(memory->array_buffer(), i_isolate);
size_t curr_size = buffer->byte_length() / i::wasm::kWasmPageSize;
DCHECK_LE(curr_size, std::numeric_limits<uint32_t>::max());
uint32_t min_size = static_cast<uint32_t>(curr_size);
base::Optional<uint32_t> max_size;
if (memory->has_maximum_pages()) {
uint64_t max_size64 = memory->maximum_pages();
DCHECK_LE(max_size64, std::numeric_limits<uint32_t>::max());
max_size.emplace(static_cast<uint32_t>(max_size64));
}
auto type = i::wasm::GetTypeForMemory(i_isolate, min_size, max_size);
args.GetReturnValue().Set(Utils::ToLocal(type));
}
void WebAssemblyGlobalGetValueCommon(
const v8::FunctionCallbackInfo<v8::Value>& args, const char* name) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
ScheduledErrorThrower thrower(i_isolate, name);
EXTRACT_THIS(receiver, WasmGlobalObject);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
switch (receiver->type().kind()) {
case i::wasm::ValueType::kI32:
return_value.Set(receiver->GetI32());
break;
case i::wasm::ValueType::kI64: {
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
if (enabled_features.has_bigint()) {
Local<BigInt> value = BigInt::New(isolate, receiver->GetI64());
return_value.Set(value);
} else {
thrower.TypeError("Can't get the value of i64 WebAssembly.Global");
}
break;
}
case i::wasm::ValueType::kF32:
return_value.Set(receiver->GetF32());
break;
case i::wasm::ValueType::kF64:
return_value.Set(receiver->GetF64());
break;
case i::wasm::ValueType::kS128:
thrower.TypeError("Can't get the value of s128 WebAssembly.Global");
break;
case i::wasm::ValueType::kRef:
case i::wasm::ValueType::kOptRef:
switch (receiver->type().heap_representation()) {
case i::wasm::HeapType::kExtern:
case i::wasm::HeapType::kFunc:
case i::wasm::HeapType::kExn:
return_value.Set(Utils::ToLocal(receiver->GetRef()));
break;
case i::wasm::HeapType::kEq:
default:
// TODO(7748): Implement these.
UNIMPLEMENTED();
break;
}
break;
case i::wasm::ValueType::kRtt:
UNIMPLEMENTED(); // TODO(7748): Implement.
break;
case i::wasm::ValueType::kI8:
case i::wasm::ValueType::kI16:
case i::wasm::ValueType::kBottom:
case i::wasm::ValueType::kStmt:
UNREACHABLE();
}
}
// WebAssembly.Global.valueOf() -> num
void WebAssemblyGlobalValueOf(const v8::FunctionCallbackInfo<v8::Value>& args) {
return WebAssemblyGlobalGetValueCommon(args, "WebAssembly.Global.valueOf()");
}
// get WebAssembly.Global.value -> num
void WebAssemblyGlobalGetValue(
const v8::FunctionCallbackInfo<v8::Value>& args) {
return WebAssemblyGlobalGetValueCommon(args, "get WebAssembly.Global.value");
}
// set WebAssembly.Global.value(num)
void WebAssemblyGlobalSetValue(
const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
HandleScope scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
ScheduledErrorThrower thrower(i_isolate, "set WebAssembly.Global.value");
EXTRACT_THIS(receiver, WasmGlobalObject);
if (!receiver->is_mutable()) {
thrower.TypeError("Can't set the value of an immutable global.");
return;
}
if (args[0]->IsUndefined()) {
thrower.TypeError("Argument 0 is required");
return;
}
switch (receiver->type().kind()) {
case i::wasm::ValueType::kI32: {
int32_t i32_value = 0;
if (!args[0]->Int32Value(context).To(&i32_value)) return;
receiver->SetI32(i32_value);
break;
}
case i::wasm::ValueType::kI64: {
auto enabled_features = i::wasm::WasmFeatures::FromIsolate(i_isolate);
if (enabled_features.has_bigint()) {
v8::Local<v8::BigInt> bigint_value;
if (!args[0]->ToBigInt(context).ToLocal(&bigint_value)) return;
receiver->SetI64(bigint_value->Int64Value());
} else {
thrower.TypeError("Can't set the value of i64 WebAssembly.Global");
}
break;
}
case i::wasm::ValueType::kF32: {
double f64_value = 0;
if (!args[0]->NumberValue(context).To(&f64_value)) return;
receiver->SetF32(i::DoubleToFloat32(f64_value));
break;
}
case i::wasm::ValueType::kF64: {
double f64_value = 0;
if (!args[0]->NumberValue(context).To(&f64_value)) return;
receiver->SetF64(f64_value);
break;
}
case i::wasm::ValueType::kS128:
thrower.TypeError("Can't set the value of s128 WebAssembly.Global");
break;
case i::wasm::ValueType::kRef:
case i::wasm::ValueType::kOptRef:
switch (receiver->type().heap_representation()) {
case i::wasm::HeapType::kExtern:
case i::wasm::HeapType::kExn:
receiver->SetExternRef(Utils::OpenHandle(*args[0]));
break;
case i::wasm::HeapType::kFunc: {
if (!receiver->SetFuncRef(i_isolate, Utils::OpenHandle(*args[0]))) {
thrower.TypeError(
"value of an funcref reference must be either null or an "
"exported function");
}
break;
}
case i::wasm::HeapType::kEq:
default:
// TODO(7748): Implement these.
UNIMPLEMENTED();
break;
}
break;
case i::wasm::ValueType::kRtt:
// TODO(7748): Implement.
UNIMPLEMENTED();
break;
case i::wasm::ValueType::kI8:
case i::wasm::ValueType::kI16:
case i::wasm::ValueType::kBottom:
case i::wasm::ValueType::kStmt:
UNREACHABLE();
}
}
// WebAssembly.Global.type(WebAssembly.Global) -> GlobalType
void WebAssemblyGlobalType(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
HandleScope scope(isolate);
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ScheduledErrorThrower thrower(i_isolate, "WebAssembly.Global.type()");
auto maybe_global = GetFirstArgumentAsGlobal(args, &thrower);
if (thrower.error()) return;
i::Handle<i::WasmGlobalObject> global = maybe_global.ToHandleChecked();
auto type = i::wasm::GetTypeForGlobal(i_isolate, global->is_mutable(),
global->type());
args.GetReturnValue().Set(Utils::ToLocal(type));
}
} // namespace
// TODO(titzer): we use the API to create the function template because the
// internal guts are too ugly to replicate here.
static i::Handle<i::FunctionTemplateInfo> NewFunctionTemplate(
i::Isolate* i_isolate, FunctionCallback func, bool has_prototype) {
Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate);
Local<FunctionTemplate> templ = FunctionTemplate::New(isolate, func);
has_prototype ? templ->ReadOnlyPrototype() : templ->RemovePrototype();
return v8::Utils::OpenHandle(*templ);
}
static i::Handle<i::ObjectTemplateInfo> NewObjectTemplate(
i::Isolate* i_isolate) {
Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate);
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
return v8::Utils::OpenHandle(*templ);
}
namespace internal {
Handle<JSFunction> CreateFunc(Isolate* isolate, Handle<String> name,
FunctionCallback func, bool has_prototype) {
Handle<FunctionTemplateInfo> temp =
NewFunctionTemplate(isolate, func, has_prototype);
Handle<JSFunction> function =
ApiNatives::InstantiateFunction(temp, name).ToHandleChecked();
DCHECK(function->shared().HasSharedName());
return function;
}
Handle<JSFunction> InstallFunc(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback func,
int length, bool has_prototype = false,
PropertyAttributes attributes = NONE) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> function = CreateFunc(isolate, name, func, has_prototype);
function->shared().set_length(length);
JSObject::AddProperty(isolate, object, name, function, attributes);
return function;
}
Handle<JSFunction> InstallConstructorFunc(Isolate* isolate,
Handle<JSObject> object,
const char* str,
FunctionCallback func) {
return InstallFunc(isolate, object, str, func, 1, true, DONT_ENUM);
}
Handle<String> GetterName(Isolate* isolate, Handle<String> name) {
return Name::ToFunctionName(isolate, name, isolate->factory()->get_string())
.ToHandleChecked();
}
void InstallGetter(Isolate* isolate, Handle<JSObject> object, const char* str,
FunctionCallback func) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> function =
CreateFunc(isolate, GetterName(isolate, name), func, false);
Utils::ToLocal(object)->SetAccessorProperty(Utils::ToLocal(name),
Utils::ToLocal(function),
Local<Function>(), v8::None);
}
Handle<String> SetterName(Isolate* isolate, Handle<String> name) {
return Name::ToFunctionName(isolate, name, isolate->factory()->set_string())
.ToHandleChecked();
}
void InstallGetterSetter(Isolate* isolate, Handle<JSObject> object,
const char* str, FunctionCallback getter,
FunctionCallback setter) {
Handle<String> name = v8_str(isolate, str);
Handle<JSFunction> getter_func =
CreateFunc(isolate, GetterName(isolate, name), getter, false);
Handle<JSFunction> setter_func =
CreateFunc(isolate, SetterName(isolate, name), setter, false);
setter_func->shared().set_length(1);
Utils::ToLocal(object)->SetAccessorProperty(
Utils::ToLocal(name), Utils::ToLocal(getter_func),
Utils::ToLocal(setter_func), v8::None);
}
// Assigns a dummy instance template to the given constructor function. Used to
// make sure the implicit receivers for the constructors in this file have an
// instance type different from the internal one, they allocate the resulting
// object explicitly and ignore implicit receiver.
void SetDummyInstanceTemplate(Isolate* isolate, Handle<JSFunction> fun) {
Handle<ObjectTemplateInfo> instance_template = NewObjectTemplate(isolate);
FunctionTemplateInfo::SetInstanceTemplate(
isolate, handle(fun->shared().get_api_func_data(), isolate),
instance_template);
}
// static
void WasmJs::Install(Isolate* isolate, bool exposed_on_global_object) {
Handle<JSGlobalObject> global = isolate->global_object();
Handle<Context> context(global->native_context(), isolate);
// Install the JS API once only.
Object prev = context->get(Context::WASM_MODULE_CONSTRUCTOR_INDEX);
if (!prev.IsUndefined(isolate)) {
DCHECK(prev.IsJSFunction());
return;
}
Factory* factory = isolate->factory();
// Setup WebAssembly
Handle<String> name = v8_str(isolate, "WebAssembly");
NewFunctionArgs args = NewFunctionArgs::ForFunctionWithoutCode(
name, isolate->strict_function_map(), LanguageMode::kStrict);
Handle<JSFunction> cons = factory->NewFunction(args);
JSFunction::SetPrototype(cons, isolate->initial_object_prototype());
Handle<JSObject> webassembly =
factory->NewJSObject(cons, AllocationType::kOld);
PropertyAttributes ro_attributes =
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
JSObject::AddProperty(isolate, webassembly, factory->to_string_tag_symbol(),
name, ro_attributes);
InstallFunc(isolate, webassembly, "compile", WebAssemblyCompile, 1);
InstallFunc(isolate, webassembly, "validate", WebAssemblyValidate, 1);
InstallFunc(isolate, webassembly, "instantiate", WebAssemblyInstantiate, 1);
if (FLAG_wasm_test_streaming) {
isolate->set_wasm_streaming_callback(WasmStreamingCallbackForTesting);
}
if (isolate->wasm_streaming_callback() != nullptr) {
InstallFunc(isolate, webassembly, "compileStreaming",
WebAssemblyCompileStreaming, 1);
InstallFunc(isolate, webassembly, "instantiateStreaming",
WebAssemblyInstantiateStreaming, 1);
}
// Expose the API on the global object if configured to do so.
if (exposed_on_global_object) {
JSObject::AddProperty(isolate, global, name, webassembly, DONT_ENUM);
}
// Setup Module
Handle<JSFunction> module_constructor =
InstallConstructorFunc(isolate, webassembly, "Module", WebAssemblyModule);
context->set_wasm_module_constructor(*module_constructor);
SetDummyInstanceTemplate(isolate, module_constructor);
JSFunction::EnsureHasInitialMap(module_constructor);
Handle<JSObject> module_proto(
JSObject::cast(module_constructor->instance_prototype()), isolate);
Handle<Map> module_map = isolate->factory()->NewMap(
i::WASM_MODULE_OBJECT_TYPE, WasmModuleObject::kHeaderSize);
JSFunction::SetInitialMap(module_constructor, module_map, module_proto);
InstallFunc(isolate, module_constructor, "imports", WebAssemblyModuleImports,
1);
InstallFunc(isolate, module_constructor, "exports", WebAssemblyModuleExports,
1);
InstallFunc(isolate, module_constructor, "customSections",
WebAssemblyModuleCustomSections, 2);
JSObject::AddProperty(isolate, module_proto, factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Module"), ro_attributes);
// Setup Instance
Handle<JSFunction> instance_constructor = InstallConstructorFunc(
isolate, webassembly, "Instance", WebAssemblyInstance);
context->set_wasm_instance_constructor(*instance_constructor);
SetDummyInstanceTemplate(isolate, instance_constructor);
JSFunction::EnsureHasInitialMap(instance_constructor);
Handle<JSObject> instance_proto(
JSObject::cast(instance_constructor->instance_prototype()), isolate);
Handle<Map> instance_map = isolate->factory()->NewMap(
i::WASM_INSTANCE_OBJECT_TYPE, WasmInstanceObject::kHeaderSize);
JSFunction::SetInitialMap(instance_constructor, instance_map, instance_proto);
InstallGetter(isolate, instance_proto, "exports",
WebAssemblyInstanceGetExports);
JSObject::AddProperty(isolate, instance_proto,
factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Instance"), ro_attributes);
// The context is not set up completely yet. That's why we cannot use
// {WasmFeatures::FromIsolate} and have to use {WasmFeatures::FromFlags}
// instead.
auto enabled_features = i::wasm::WasmFeatures::FromFlags();
// Setup Table
Handle<JSFunction> table_constructor =
InstallConstructorFunc(isolate, webassembly, "Table", WebAssemblyTable);
context->set_wasm_table_constructor(*table_constructor);
SetDummyInstanceTemplate(isolate, table_constructor);
JSFunction::EnsureHasInitialMap(table_constructor);
Handle<JSObject> table_proto(
JSObject::cast(table_constructor->instance_prototype()), isolate);
Handle<Map> table_map = isolate->factory()->NewMap(
i::WASM_TABLE_OBJECT_TYPE, WasmTableObject::kHeaderSize);
JSFunction::SetInitialMap(table_constructor, table_map, table_proto);
InstallGetter(isolate, table_proto, "length", WebAssemblyTableGetLength);
InstallFunc(isolate, table_proto, "grow", WebAssemblyTableGrow, 1);
InstallFunc(isolate, table_proto, "get", WebAssemblyTableGet, 1);
InstallFunc(isolate, table_proto, "set", WebAssemblyTableSet, 2);
if (enabled_features.has_type_reflection()) {
InstallFunc(isolate, table_constructor, "type", WebAssemblyTableType, 1);
}
JSObject::AddProperty(isolate, table_proto, factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Table"), ro_attributes);
// Setup Memory
Handle<JSFunction> memory_constructor =
InstallConstructorFunc(isolate, webassembly, "Memory", WebAssemblyMemory);
context->set_wasm_memory_constructor(*memory_constructor);
SetDummyInstanceTemplate(isolate, memory_constructor);
JSFunction::EnsureHasInitialMap(memory_constructor);
Handle<JSObject> memory_proto(
JSObject::cast(memory_constructor->instance_prototype()), isolate);
Handle<Map> memory_map = isolate->factory()->NewMap(
i::WASM_MEMORY_OBJECT_TYPE, WasmMemoryObject::kHeaderSize);
JSFunction::SetInitialMap(memory_constructor, memory_map, memory_proto);
InstallFunc(isolate, memory_proto, "grow", WebAssemblyMemoryGrow, 1);
InstallGetter(isolate, memory_proto, "buffer", WebAssemblyMemoryGetBuffer);
if (enabled_features.has_type_reflection()) {
InstallFunc(isolate, memory_constructor, "type", WebAssemblyMemoryType, 1);
}
JSObject::AddProperty(isolate, memory_proto, factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Memory"), ro_attributes);
// Setup Global
Handle<JSFunction> global_constructor =
InstallConstructorFunc(isolate, webassembly, "Global", WebAssemblyGlobal);
context->set_wasm_global_constructor(*global_constructor);
SetDummyInstanceTemplate(isolate, global_constructor);
JSFunction::EnsureHasInitialMap(global_constructor);
Handle<JSObject> global_proto(
JSObject::cast(global_constructor->instance_prototype()), isolate);
Handle<Map> global_map = isolate->factory()->NewMap(
i::WASM_GLOBAL_OBJECT_TYPE, WasmGlobalObject::kHeaderSize);
JSFunction::SetInitialMap(global_constructor, global_map, global_proto);
InstallFunc(isolate, global_proto, "valueOf", WebAssemblyGlobalValueOf, 0);
InstallGetterSetter(isolate, global_proto, "value", WebAssemblyGlobalGetValue,
WebAssemblyGlobalSetValue);
if (enabled_features.has_type_reflection()) {
InstallFunc(isolate, global_constructor, "type", WebAssemblyGlobalType, 1);
}
JSObject::AddProperty(isolate, global_proto, factory->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Global"), ro_attributes);
// Setup Exception
if (enabled_features.has_eh()) {
Handle<JSFunction> exception_constructor = InstallConstructorFunc(
isolate, webassembly, "Exception", WebAssemblyException);
context->set_wasm_exception_constructor(*exception_constructor);
SetDummyInstanceTemplate(isolate, exception_constructor);
JSFunction::EnsureHasInitialMap(exception_constructor);
Handle<JSObject> exception_proto(
JSObject::cast(exception_constructor->instance_prototype()), isolate);
Handle<Map> exception_map = isolate->factory()->NewMap(
i::WASM_EXCEPTION_OBJECT_TYPE, WasmExceptionObject::kHeaderSize);
JSFunction::SetInitialMap(exception_constructor, exception_map,
exception_proto);
}
// Setup Function
if (enabled_features.has_type_reflection()) {
Handle<JSFunction> function_constructor = InstallConstructorFunc(
isolate, webassembly, "Function", WebAssemblyFunction);
SetDummyInstanceTemplate(isolate, function_constructor);
JSFunction::EnsureHasInitialMap(function_constructor);
Handle<JSObject> function_proto(
JSObject::cast(function_constructor->instance_prototype()), isolate);
Handle<Map> function_map = isolate->factory()->CreateSloppyFunctionMap(
FUNCTION_WITHOUT_PROTOTYPE, MaybeHandle<JSFunction>());
CHECK(JSObject::SetPrototype(
function_proto,
handle(context->function_function().prototype(), isolate), false,
kDontThrow)
.FromJust());
JSFunction::SetInitialMap(function_constructor, function_map,
function_proto);
InstallFunc(isolate, function_constructor, "type", WebAssemblyFunctionType,
1);
// Make all exported functions an instance of {WebAssembly.Function}.
context->set_wasm_exported_function_map(*function_map);
} else {
// Make all exported functions an instance of {Function}.
Handle<Map> function_map = isolate->sloppy_function_without_prototype_map();
context->set_wasm_exported_function_map(*function_map);
}
// Setup errors
Handle<JSFunction> compile_error(
isolate->native_context()->wasm_compile_error_function(), isolate);
JSObject::AddProperty(isolate, webassembly,
isolate->factory()->CompileError_string(),
compile_error, DONT_ENUM);
Handle<JSFunction> link_error(
isolate->native_context()->wasm_link_error_function(), isolate);
JSObject::AddProperty(isolate, webassembly,
isolate->factory()->LinkError_string(), link_error,
DONT_ENUM);
Handle<JSFunction> runtime_error(
isolate->native_context()->wasm_runtime_error_function(), isolate);
JSObject::AddProperty(isolate, webassembly,
isolate->factory()->RuntimeError_string(),
runtime_error, DONT_ENUM);
}
namespace {
void SetMapValue(Isolate* isolate, Handle<JSMap> map, Handle<Object> key,
Handle<Object> value) {
DCHECK(!map.is_null() && !key.is_null() && !value.is_null());
Handle<Object> argv[] = {key, value};
Execution::CallBuiltin(isolate, isolate->map_set(), map, arraysize(argv),
argv)
.Check();
}
Handle<Object> GetMapValue(Isolate* isolate, Handle<JSMap> map,
Handle<Object> key) {
DCHECK(!map.is_null() && !key.is_null());
Handle<Object> argv[] = {key};
return Execution::CallBuiltin(isolate, isolate->map_get(), map,
arraysize(argv), argv)
.ToHandleChecked();
}
// Look up a name in a name table. Name tables are stored under the "names"
// property of the handler and map names to index.
base::Optional<int> ResolveValueSelector(Isolate* isolate,
Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
size_t index = 0;
if (enable_index_lookup && property->AsIntegerIndex(&index) &&
index < kMaxInt) {
return static_cast<int>(index);
}
Handle<Object> name_table =
JSObject::GetProperty(isolate, handler, "names").ToHandleChecked();
DCHECK(name_table->IsJSMap());
Handle<Object> object =
GetMapValue(isolate, Handle<JSMap>::cast(name_table), property);
if (object->IsUndefined()) return {};
DCHECK(object->IsNumeric());
return NumberToInt32(*object);
}
// Helper for unpacking a maybe name that makes a default with an index if
// the name is empty. If the name is not empty, it's prefixed with a $.
Handle<String> GetNameOrDefault(Isolate* isolate,
MaybeHandle<String> maybe_name,
const char* default_name_prefix, int index) {
Handle<String> name;
if (maybe_name.ToHandle(&name)) {
return isolate->factory()
->NewConsString(isolate->factory()->NewStringFromAsciiChecked("$"),
name)
.ToHandleChecked();
}
// Maximum length of the default names: $memory-2147483648\0
static constexpr int kMaxStrLen = 19;
EmbeddedVector<char, kMaxStrLen> value;
DCHECK_LT(strlen(default_name_prefix) + /*strlen(kMinInt)*/ 11, kMaxStrLen);
int len = SNPrintF(value, "%s%d", default_name_prefix, index);
return isolate->factory()->InternalizeString(value.SubVector(0, len));
}
// Generate names for the locals. Names either come from the name table,
// otherwise the default $varX is used.
std::vector<Handle<String>> GetLocalNames(Handle<WasmInstanceObject> instance,
Address pc) {
wasm::NativeModule* native_module = instance->module_object().native_module();
wasm::DebugInfo* debug_info = native_module->GetDebugInfo();
int num_locals = debug_info->GetNumLocals(pc);
auto* isolate = instance->GetIsolate();
wasm::ModuleWireBytes module_wire_bytes(
instance->module_object().native_module()->wire_bytes());
const wasm::WasmFunction& function = debug_info->GetFunctionAtAddress(pc);
std::vector<Handle<String>> names;
for (int i = 0; i < num_locals; ++i) {
wasm::WireBytesRef local_name_ref =
debug_info->GetLocalName(function.func_index, i);
DCHECK(module_wire_bytes.BoundsCheck(local_name_ref));
Vector<const char> name_vec =
module_wire_bytes.GetNameOrNull(local_name_ref);
names.emplace_back(GetNameOrDefault(
isolate,
name_vec.empty() ? MaybeHandle<String>()
: isolate->factory()->NewStringFromUtf8(name_vec),
"$var", i));
}
return names;
}
// Generate names for the globals. Names either come from the name table,
// otherwise the default $globalX is used.
std::vector<Handle<String>> GetGlobalNames(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
auto& globals = instance->module()->globals;
std::vector<Handle<String>> names;
for (uint32_t i = 0; i < globals.size(); ++i) {
names.emplace_back(GetNameOrDefault(
isolate, WasmInstanceObject::GetGlobalNameOrNull(isolate, instance, i),
"$global", i));
}
return names;
}
// Generate names for the functions.
std::vector<Handle<String>> GetFunctionNames(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
auto* module = instance->module();
wasm::ModuleWireBytes wire_bytes(
instance->module_object().native_module()->wire_bytes());
std::vector<Handle<String>> names;
for (auto& function : module->functions) {
DCHECK_EQ(function.func_index, names.size());
wasm::WireBytesRef name_ref =
module->lazily_generated_names.LookupFunctionName(
wire_bytes, function.func_index, VectorOf(module->export_table));
DCHECK(wire_bytes.BoundsCheck(name_ref));
Vector<const char> name_vec = wire_bytes.GetNameOrNull(name_ref);
names.emplace_back(GetNameOrDefault(
isolate,
name_vec.empty() ? MaybeHandle<String>()
: isolate->factory()->NewStringFromUtf8(name_vec),
"$func", function.func_index));
}
return names;
}
// Generate names for the imports.
std::vector<Handle<String>> GetImportNames(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
const wasm::WasmModule* module = instance->module();
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
int num_imports = static_cast<int>(module->import_table.size());
std::vector<Handle<String>> names;
for (int index = 0; index < num_imports; ++index) {
const wasm::WasmImport& import = module->import_table[index];
names.emplace_back(WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate, module_object, import.field_name, kInternalize));
}
return names;
}
// Generate names for the memories.
std::vector<Handle<String>> GetMemoryNames(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
std::vector<Handle<String>> names;
uint32_t memory_count = instance->has_memory_object() ? 1 : 0;
for (uint32_t memory_index = 0; memory_index < memory_count; ++memory_index) {
names.emplace_back(GetNameOrDefault(isolate,
WasmInstanceObject::GetMemoryNameOrNull(
isolate, instance, memory_index),
"$memory", memory_index));
}
return names;
}
// Generate names for the tables.
std::vector<Handle<String>> GetTableNames(Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
auto tables = handle(instance->tables(), isolate);
std::vector<Handle<String>> names;
for (int table_index = 0; table_index < tables->length(); ++table_index) {
auto func_table =
handle(WasmTableObject::cast(tables->get(table_index)), isolate);
if (!func_table->type().is_reference_to(wasm::HeapType::kFunc)) continue;
names.emplace_back(GetNameOrDefault(
isolate,
WasmInstanceObject::GetTableNameOrNull(isolate, instance, table_index),
"$table", table_index));
}
return names;
}
// Generate names for the exports
std::vector<Handle<String>> GetExportNames(
Handle<WasmInstanceObject> instance) {
Isolate* isolate = instance->GetIsolate();
const wasm::WasmModule* module = instance->module();
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
int num_exports = static_cast<int>(module->export_table.size());
std::vector<Handle<String>> names;
for (int index = 0; index < num_exports; ++index) {
const wasm::WasmExport& exp = module->export_table[index];
names.emplace_back(WasmModuleObject::ExtractUtf8StringFromModuleBytes(
isolate, module_object, exp.name, kInternalize));
}
return names;
}
Handle<WasmInstanceObject> GetInstance(Isolate* isolate,
Handle<JSObject> handler) {
Handle<Object> instance =
JSObject::GetProperty(isolate, handler, "instance").ToHandleChecked();
DCHECK(instance->IsWasmInstanceObject());
return Handle<WasmInstanceObject>::cast(instance);
}
Address GetPC(Isolate* isolate, Handle<JSObject> handler) {
Handle<Object> pc =
JSObject::GetProperty(isolate, handler, "pc").ToHandleChecked();
DCHECK(pc->IsBigInt());
return Handle<BigInt>::cast(pc)->AsUint64();
}
Address GetFP(Isolate* isolate, Handle<JSObject> handler) {
Handle<Object> fp =
JSObject::GetProperty(isolate, handler, "fp").ToHandleChecked();
DCHECK(fp->IsBigInt());
return Handle<BigInt>::cast(fp)->AsUint64();
}
Address GetCalleeFP(Isolate* isolate, Handle<JSObject> handler) {
Handle<Object> callee_fp =
JSObject::GetProperty(isolate, handler, "callee_fp").ToHandleChecked();
DCHECK(callee_fp->IsBigInt());
return Handle<BigInt>::cast(callee_fp)->AsUint64();
}
// Convert a WasmValue to an appropriate JS representation.
static Handle<Object> WasmValueToObject(Isolate* isolate,
wasm::WasmValue value) {
auto* factory = isolate->factory();
switch (value.type().kind()) {
case wasm::ValueType::kI32:
return factory->NewNumberFromInt(value.to_i32());
case wasm::ValueType::kI64:
return BigInt::FromInt64(isolate, value.to_i64());
case wasm::ValueType::kF32:
return factory->NewNumber(value.to_f32());
case wasm::ValueType::kF64:
return factory->NewNumber(value.to_f64());
case wasm::ValueType::kS128: {
wasm::Simd128 s128 = value.to_s128();
Handle<JSArrayBuffer> buffer;
if (!isolate->factory()
->NewJSArrayBufferAndBackingStore(
kSimd128Size, InitializedFlag::kUninitialized)
.ToHandle(&buffer)) {
isolate->FatalProcessOutOfHeapMemory(
"failed to allocate backing store");
}
memcpy(buffer->allocation_base(), s128.bytes(),
buffer->byte_length());
return isolate->factory()->NewJSTypedArray(kExternalUint8Array, buffer, 0,
buffer->byte_length());
}
case wasm::ValueType::kRef:
return value.to_externref();
default:
break;
}
return factory->undefined_value();
}
base::Optional<int> HasLocalImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
Address pc = GetPC(isolate, handler);
wasm::DebugInfo* debug_info =
instance->module_object().native_module()->GetDebugInfo();
int num_locals = debug_info->GetNumLocals(pc);
if (0 <= index && index < num_locals) return index;
return {};
}
Handle<Object> GetLocalImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Factory* factory = isolate->factory();
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasLocalImpl(isolate, property, handler, enable_index_lookup);
if (!index) return factory->undefined_value();
Address pc = GetPC(isolate, handler);
Address fp = GetFP(isolate, handler);
Address callee_fp = GetCalleeFP(isolate, handler);
wasm::DebugInfo* debug_info =
instance->module_object().native_module()->GetDebugInfo();
wasm::WasmValue value = debug_info->GetLocalValue(*index, pc, fp, callee_fp);
return WasmValueToObject(isolate, value);
}
base::Optional<int> HasGlobalImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
const std::vector<wasm::WasmGlobal>& globals = instance->module()->globals;
if (globals.size() <= kMaxInt && 0 <= *index &&
*index < static_cast<int>(globals.size())) {
return index;
}
return {};
}
Handle<Object> GetGlobalImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasGlobalImpl(isolate, property, handler, enable_index_lookup);
if (!index) return isolate->factory()->undefined_value();
const std::vector<wasm::WasmGlobal>& globals = instance->module()->globals;
return WasmValueToObject(
isolate, WasmInstanceObject::GetGlobalValue(instance, globals[*index]));
}
base::Optional<int> HasMemoryImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (index && *index == 0 && instance->has_memory_object()) return index;
return {};
}
Handle<Object> GetMemoryImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasMemoryImpl(isolate, property, handler, enable_index_lookup);
if (index) return handle(instance->memory_object(), isolate);
return isolate->factory()->undefined_value();
}
base::Optional<int> HasFunctionImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
const std::vector<wasm::WasmFunction>& functions =
instance->module()->functions;
if (functions.size() <= kMaxInt && 0 <= *index &&
*index < static_cast<int>(functions.size())) {
return index;
}
return {};
}
Handle<Object> GetFunctionImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasFunctionImpl(isolate, property, handler, enable_index_lookup);
if (!index) return isolate->factory()->undefined_value();
return WasmInstanceObject::GetOrCreateWasmExternalFunction(isolate, instance,
*index);
}
base::Optional<int> HasTableImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
Handle<FixedArray> tables(instance->tables(), isolate);
int num_tables = tables->length();
if (*index < 0 || *index >= num_tables) return {};
Handle<WasmTableObject> func_table(WasmTableObject::cast(tables->get(*index)),
isolate);
if (func_table->type().is_reference_to(wasm::HeapType::kFunc)) return index;
return {};
}
Handle<Object> GetTableImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasTableImpl(isolate, property, handler, enable_index_lookup);
if (!index) return isolate->factory()->undefined_value();
Handle<WasmTableObject> func_table(
WasmTableObject::cast(instance->tables().get(*index)), isolate);
return func_table;
}
base::Optional<int> HasImportImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
const wasm::WasmModule* module = instance->module();
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
int num_imports = static_cast<int>(module->import_table.size());
if (0 <= *index && *index < num_imports) return index;
return {};
}
Handle<JSObject> GetExternalObject(Isolate* isolate,
wasm::ImportExportKindCode kind,
uint32_t index) {
Handle<JSObject> result = isolate->factory()->NewJSObjectWithNullProto();
Handle<Object> value = isolate->factory()->NewNumberFromUint(index);
switch (kind) {
case wasm::kExternalFunction:
JSObject::AddProperty(isolate, result, "func", value, NONE);
break;
case wasm::kExternalGlobal:
JSObject::AddProperty(isolate, result, "global", value, NONE);
break;
case wasm::kExternalTable:
JSObject::AddProperty(isolate, result, "table", value, NONE);
break;
case wasm::kExternalMemory:
JSObject::AddProperty(isolate, result, "mem", value, NONE);
break;
case wasm::kExternalException:
JSObject::AddProperty(isolate, result, "exn", value, NONE);
break;
}
return result;
}
Handle<Object> GetImportImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasImportImpl(isolate, property, handler, enable_index_lookup);
if (!index) return isolate->factory()->undefined_value();
const wasm::WasmImport& imp = instance->module()->import_table[*index];
return GetExternalObject(isolate, imp.kind, imp.index);
}
base::Optional<int> HasExportImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
ResolveValueSelector(isolate, property, handler, enable_index_lookup);
if (!index) return index;
const wasm::WasmModule* module = instance->module();
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
int num_exports = static_cast<int>(module->export_table.size());
if (0 <= *index && *index < num_exports) return index;
return {};
}
Handle<Object> GetExportImpl(Isolate* isolate, Handle<Name> property,
Handle<JSObject> handler,
bool enable_index_lookup) {
Handle<WasmInstanceObject> instance = GetInstance(isolate, handler);
base::Optional<int> index =
HasExportImpl(isolate, property, handler, enable_index_lookup);
if (!index) return isolate->factory()->undefined_value();
const wasm::WasmExport& exp = instance->module()->export_table[*index];
return GetExternalObject(isolate, exp.kind, exp.index);
}
// Generic has trap callback for the index space proxies.
template <base::Optional<int> Impl(Isolate*, Handle<Name>, Handle<JSObject>,
bool)>
void HasTrapCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
DCHECK_GE(args.Length(), 2);
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
DCHECK(args.This()->IsObject());
Handle<JSObject> handler =
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
DCHECK(args[1]->IsName());
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
args.GetReturnValue().Set(Impl(isolate, property, handler, true).has_value());
}
// Generic get trap callback for the index space proxies.
template <Handle<Object> Impl(Isolate*, Handle<Name>, Handle<JSObject>, bool)>
void GetTrapCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
DCHECK_GE(args.Length(), 2);
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
DCHECK(args.This()->IsObject());
Handle<JSObject> handler =
Handle<JSObject>::cast(Utils::OpenHandle(*args.This()));
DCHECK(args[1]->IsName());
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
args.GetReturnValue().Set(
Utils::ToLocal(Impl(isolate, property, handler, true)));
}
template <typename ReturnT>
ReturnT DelegateToplevelCall(Isolate* isolate, Handle<JSObject> target,
Handle<Name> property, const char* index_space,
ReturnT (*impl)(Isolate*, Handle<Name>,
Handle<JSObject>, bool)) {
Handle<Object> namespace_proxy =
JSObject::GetProperty(isolate, target, index_space).ToHandleChecked();
DCHECK(namespace_proxy->IsJSProxy());
Handle<JSObject> namespace_handler(
JSObject::cast(Handle<JSProxy>::cast(namespace_proxy)->handler()),
isolate);
return impl(isolate, property, namespace_handler, false);
}
template <typename ReturnT>
using DelegateCallback = ReturnT (*)(Isolate*, Handle<Name>, Handle<JSObject>,
bool);
// Has trap callback for the top-level proxy.
void ToplevelHasTrapCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
DCHECK_GE(args.Length(), 2);
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
DCHECK(args[0]->IsObject());
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
DCHECK(args[1]->IsName());
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
// First check if the property exists on the target.
if (JSObject::HasProperty(target, property).FromMaybe(false)) {
args.GetReturnValue().Set(true);
return;
}
// Now check the index space proxies in order if they know the property.
constexpr std::pair<const char*, DelegateCallback<base::Optional<int>>>
kDelegates[] = {{"memories", HasMemoryImpl},
{"locals", HasLocalImpl},
{"tables", HasTableImpl},
{"functions", HasFunctionImpl},
{"globals", HasGlobalImpl}};
for (auto& delegate : kDelegates) {
if (DelegateToplevelCall(isolate, target, property, delegate.first,
delegate.second)) {
args.GetReturnValue().Set(true);
return;
}
args.GetReturnValue().Set(false);
}
}
// Get trap callback for the top-level proxy.
void ToplevelGetTrapCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
DCHECK_GE(args.Length(), 2);
Isolate* isolate = reinterpret_cast<Isolate*>(args.GetIsolate());
DCHECK(args[0]->IsObject());
Handle<JSObject> target = Handle<JSObject>::cast(Utils::OpenHandle(*args[0]));
DCHECK(args[1]->IsName());
Handle<Name> property = Handle<Name>::cast(Utils::OpenHandle(*args[1]));
// First, check if the property is a proper property on the target. If so,
// return its value.
Handle<Object> value =
JSObject::GetProperty(isolate, target, property).ToHandleChecked();
if (!value->IsUndefined()) {
args.GetReturnValue().Set(Utils::ToLocal(value));
return;
}
// Try the index space proxies in the correct disambiguation order.
constexpr std::pair<const char*, DelegateCallback<Handle<Object>>>
kDelegates[] = {{"memories", GetMemoryImpl},
{"locals", GetLocalImpl},
{"tables", GetTableImpl},
{"functions", GetFunctionImpl},
{"globals", GetGlobalImpl}};
for (auto& delegate : kDelegates) {
value = DelegateToplevelCall(isolate, target, property, delegate.first,
delegate.second);
if (!value->IsUndefined()) {
args.GetReturnValue().Set(Utils::ToLocal(value));
return;
}
}
}
// Populate a JSMap with name->index mappings from an ordered list of names.
Handle<JSMap> GetNameTable(Isolate* isolate,
const std::vector<Handle<String>>& names) {
Factory* factory = isolate->factory();
Handle<JSMap> name_table = factory->NewJSMap();
for (size_t i = 0; i < names.size(); ++i) {
SetMapValue(isolate, name_table, names[i], factory->NewNumberFromInt64(i));
}
return name_table;
}
// Produce a JSProxy with a given name table and get and has trap handlers.
Handle<JSProxy> GetJSProxy(
WasmFrame* frame, Handle<JSMap> name_table,
void (*get_callback)(const v8::FunctionCallbackInfo<v8::Value>&),
void (*has_callback)(const v8::FunctionCallbackInfo<v8::Value>&)) {
Isolate* isolate = frame->isolate();
Factory* factory = isolate->factory();
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
// Besides the name table, the get and has traps need access to the instance
// and frame information.
JSObject::AddProperty(isolate, handler, "names", name_table, DONT_ENUM);
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
JSObject::AddProperty(isolate, handler, "instance", instance, DONT_ENUM);
Handle<BigInt> pc = BigInt::FromInt64(isolate, frame->pc());
JSObject::AddProperty(isolate, handler, "pc", pc, DONT_ENUM);
Handle<BigInt> fp = BigInt::FromInt64(isolate, frame->fp());
JSObject::AddProperty(isolate, handler, "fp", fp, DONT_ENUM);
Handle<BigInt> callee_fp = BigInt::FromInt64(isolate, frame->callee_fp());
JSObject::AddProperty(isolate, handler, "callee_fp", callee_fp, DONT_ENUM);
InstallFunc(isolate, handler, "get", get_callback, 3, false, READ_ONLY);
InstallFunc(isolate, handler, "has", has_callback, 2, false, READ_ONLY);
return factory->NewJSProxy(target, handler);
}
Handle<JSObject> GetStackObject(WasmFrame* frame) {
Isolate* isolate = frame->isolate();
Handle<JSObject> object = isolate->factory()->NewJSObjectWithNullProto();
wasm::DebugInfo* debug_info =
frame->wasm_instance().module_object().native_module()->GetDebugInfo();
int num_values = debug_info->GetStackDepth(frame->pc());
for (int i = 0; i < num_values; ++i) {
wasm::WasmValue value = debug_info->GetStackValue(
i, frame->pc(), frame->fp(), frame->callee_fp());
JSObject::AddDataElement(object, i, WasmValueToObject(isolate, value),
NONE);
}
return object;
}
} // namespace
// This function generates the JS debug proxy for a given Wasm frame. The debug
// proxy is used when evaluating debug JS expressions on a wasm frame and let's
// the developer inspect the engine state from JS. The proxy provides the
// following interface:
//
// type WasmSimdValue = Uint8Array;
// type WasmValue = number | bigint | object | WasmSimdValue;
// type WasmFunction = (... args : WasmValue[]) = > WasmValue;
// type WasmExport = {name : string} & ({func : number} | {table : number} |
// {mem : number} | {global : number});
// type WasmImport = {name : string, module : string} &
// ({func : number} | {table : number} | {mem : number} |
// {global : number});
// interface WasmInterface {
// $globalX: WasmValue;
// $varX: WasmValue;
// $funcX(a : WasmValue /*, ...*/) : WasmValue;
// readonly $memoryX : WebAssembly.Memory;
// readonly $tableX : WebAssembly.Table;
// readonly memories : {[nameOrIndex:string | number] : WebAssembly.Memory};
// readonly tables : {[nameOrIndex:string | number] : WebAssembly.Table};
// readonly stack : WasmValue[];
// readonly imports : {[nameOrIndex:string | number] : WasmImport};
// readonly exports : {[nameOrIndex:string | number] : WasmExport};
// readonly globals : {[nameOrIndex:string | number] : WasmValue};
// readonly locals : {[nameOrIndex:string | number] : WasmValue};
// readonly functions : {[nameOrIndex:string | number] : WasmFunction};
// }
//
// The wasm index spaces memories, tables, imports, exports, globals, locals
// functions are JSProxies that lazily produce values either by index or by
// name. A top level JSProxy is wrapped around those for top-level lookup of
// names in the disambiguation order  memory, local, table, function, global.
// Import and export names are not globally resolved.
Handle<JSProxy> WasmJs::GetJSDebugProxy(WasmFrame* frame) {
Isolate* isolate = frame->isolate();
Factory* factory = isolate->factory();
Handle<WasmInstanceObject> instance(frame->wasm_instance(), isolate);
// The top level proxy delegates lookups to the index space proxies.
Handle<JSObject> handler = factory->NewJSObjectWithNullProto();
InstallFunc(isolate, handler, "get", ToplevelGetTrapCallback, 3, false,
READ_ONLY);
InstallFunc(isolate, handler, "has", ToplevelHasTrapCallback, 2, false,
READ_ONLY);
Handle<JSObject> target = factory->NewJSObjectWithNullProto();
// Generate JSMaps per index space for name->index lookup. Every index space
// proxy is associated with its table for local name lookup.
auto local_name_table =
GetNameTable(isolate, GetLocalNames(instance, frame->pc()));
auto locals =
GetJSProxy(frame, local_name_table, GetTrapCallback<GetLocalImpl>,
HasTrapCallback<HasLocalImpl>);
JSObject::AddProperty(isolate, target, "locals", locals, READ_ONLY);
auto global_name_table = GetNameTable(isolate, GetGlobalNames(instance));
auto globals =
GetJSProxy(frame, global_name_table, GetTrapCallback<GetGlobalImpl>,
HasTrapCallback<HasGlobalImpl>);
JSObject::AddProperty(isolate, target, "globals", globals, READ_ONLY);
auto function_name_table = GetNameTable(isolate, GetFunctionNames(instance));
auto functions =
GetJSProxy(frame, function_name_table, GetTrapCallback<GetFunctionImpl>,
HasTrapCallback<HasFunctionImpl>);
JSObject::AddProperty(isolate, target, "functions", functions, READ_ONLY);
auto memory_name_table = GetNameTable(isolate, GetMemoryNames(instance));
auto memories =
GetJSProxy(frame, memory_name_table, GetTrapCallback<GetMemoryImpl>,
HasTrapCallback<HasMemoryImpl>);
JSObject::AddProperty(isolate, target, "memories", memories, READ_ONLY);
auto table_name_table = GetNameTable(isolate, GetTableNames(instance));
auto tables =
GetJSProxy(frame, table_name_table, GetTrapCallback<GetTableImpl>,
HasTrapCallback<HasTableImpl>);
JSObject::AddProperty(isolate, target, "tables", tables, READ_ONLY);
auto import_name_table = GetNameTable(isolate, GetImportNames(instance));
auto imports =
GetJSProxy(frame, import_name_table, GetTrapCallback<GetImportImpl>,
HasTrapCallback<HasImportImpl>);
JSObject::AddProperty(isolate, target, "imports", imports, READ_ONLY);
auto export_name_table = GetNameTable(isolate, GetExportNames(instance));
auto exports =
GetJSProxy(frame, export_name_table, GetTrapCallback<GetExportImpl>,
HasTrapCallback<HasExportImpl>);
JSObject::AddProperty(isolate, target, "exports", exports, READ_ONLY);
auto stack = GetStackObject(frame);
JSObject::AddProperty(isolate, target, "stack", stack, READ_ONLY);
return factory->NewJSProxy(target, handler);
}
#undef ASSIGN
#undef EXTRACT_THIS
} // namespace internal
} // namespace v8