blob: dc1e0ca3a5099b55bf9aa222e49318b6a5785653 [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 <optional>
#include "include/v8-function.h"
#include "include/v8-persistent-handle.h"
#include "include/v8-promise.h"
#include "include/v8-wasm.h"
#include "src/api/api-inl.h"
#include "src/api/api-natives.h"
#include "src/base/fpu.h"
#include "src/base/logging.h"
#include "src/execution/execution.h"
#include "src/execution/isolate.h"
#include "src/execution/messages.h"
#include "src/flags/flags.h"
#include "src/handles/handles.h"
#include "src/heap/factory.h"
#include "src/objects/fixed-array.h"
#include "src/objects/instance-type.h"
#include "src/objects/js-function.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/shared-function-info.h"
#include "src/objects/templates.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"
namespace v8 {
using i::wasm::AddressType;
using i::wasm::CompileTimeImport;
using i::wasm::CompileTimeImports;
using i::wasm::ErrorThrower;
using i::wasm::WasmEnabledFeatures;
namespace internal {
// Note: The implementation of this function is in runtime-wasm.cc, in order
// to be able to use helpers that aren't visible outside that file.
void ToUtf8Lossy(Isolate* isolate, DirectHandle<String> string,
std::string& out);
} // namespace internal
class WasmStreaming::WasmStreamingImpl {
public:
WasmStreamingImpl(
i::Isolate* isolate, const char* api_method_name,
CompileTimeImports compile_imports,
std::shared_ptr<internal::wasm::CompilationResultResolver> resolver)
: i_isolate_(isolate),
enabled_features_(WasmEnabledFeatures::FromIsolate(i_isolate_)),
streaming_decoder_(i::wasm::GetWasmEngine()->StartStreamingCompilation(
i_isolate_, enabled_features_, std::move(compile_imports),
direct_handle(i_isolate_->context(), i_isolate_), api_method_name,
resolver)),
resolver_(std::move(resolver)) {}
void OnBytesReceived(const uint8_t* bytes, size_t size) {
streaming_decoder_->OnBytesReceived(base::VectorOf(bytes, size));
}
void Finish(bool can_use_compiled_module) {
streaming_decoder_->Finish(can_use_compiled_module);
}
void Abort(MaybeLocal<Value> exception) {
i::HandleScope scope(i_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::OpenDirectHandle(*exception.ToLocalChecked()));
}
bool SetCompiledModuleBytes(base::Vector<const uint8_t> bytes) {
if (!i::wasm::IsSupportedVersion(bytes, enabled_features_)) return false;
streaming_decoder_->SetCompiledModuleBytes(bytes);
return true;
}
void SetMoreFunctionsCanBeSerializedCallback(
std::function<void(CompiledWasmModule)> callback) {
streaming_decoder_->SetMoreFunctionsCanBeSerializedCallback(
[callback = std::move(callback),
url = streaming_decoder_->shared_url()](
const std::shared_ptr<i::wasm::NativeModule>& native_module) {
callback(CompiledWasmModule{native_module, url->data(), url->size()});
});
}
void SetUrl(base::Vector<const char> url) { streaming_decoder_->SetUrl(url); }
private:
i::Isolate* const i_isolate_;
const WasmEnabledFeatures enabled_features_;
const std::shared_ptr<internal::wasm::StreamingDecoder> streaming_decoder_;
const 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(bool can_use_compiled_module) {
TRACE_EVENT0("v8.wasm", "wasm.FinishStreaming");
impl_->Finish(can_use_compiled_module);
}
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(base::VectorOf(bytes, size));
}
void WasmStreaming::SetMoreFunctionsCanBeSerializedCallback(
std::function<void(CompiledWasmModule)> callback) {
impl_->SetMoreFunctionsCanBeSerializedCallback(std::move(callback));
}
void WasmStreaming::SetUrl(const char* url, size_t length) {
DCHECK_EQ('\0', url[length]); // {url} is null-terminated.
TRACE_EVENT1("v8.wasm", "wasm.SetUrl", "url", url);
impl_->SetUrl(base::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::Cast<i::Managed<WasmStreaming>>(Utils::OpenDirectHandle(*value));
return managed->get();
}
namespace {
#define ASSIGN(type, var, expr) \
Local<type> var; \
do { \
if (!expr.ToLocal(&var)) { \
DCHECK(i_isolate->has_exception()); \
return; \
} else { \
if (i_isolate->is_execution_terminating()) return; \
DCHECK(!i_isolate->has_exception()); \
} \
} while (false)
i::DirectHandle<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::MaybeDirectHandle<i::Wasm##Type##Object> GetFirstArgumentAs##Type( \
const v8::FunctionCallbackInfo<v8::Value>& info, \
ErrorThrower* thrower) { \
i::DirectHandle<i::Object> arg0 = Utils::OpenDirectHandle(*info[0]); \
if (!IsWasm##Type##Object(*arg0)) { \
thrower->TypeError("Argument 0 must be a WebAssembly." #Type); \
return {}; \
} \
return i::Cast<i::Wasm##Type##Object>(arg0); \
}
GET_FIRST_ARGUMENT_AS(Module)
GET_FIRST_ARGUMENT_AS(Tag)
#undef GET_FIRST_ARGUMENT_AS
base::Vector<const uint8_t> GetFirstArgumentAsBytes(
const v8::FunctionCallbackInfo<v8::Value>& info, size_t max_length,
ErrorThrower* thrower, bool* is_shared) {
const uint8_t* start = nullptr;
size_t length = 0;
v8::Local<v8::Value> source = info[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");
return {};
}
DCHECK_IMPLIES(length, start != nullptr);
if (length == 0) {
thrower->CompileError("BufferSource argument is empty");
return {};
}
if (length > max_length) {
// The spec requires a CompileError for implementation-defined limits, see
// https://webassembly.github.io/spec/js-api/index.html#limits.
thrower->CompileError("buffer source exceeds maximum size of %zu (is %zu)",
max_length, length);
return {};
}
return base::VectorOf(start, length);
}
base::OwnedVector<const uint8_t> GetAndCopyFirstArgumentAsBytes(
const v8::FunctionCallbackInfo<v8::Value>& info, size_t max_length,
ErrorThrower* thrower) {
bool is_shared = false;
base::Vector<const uint8_t> bytes =
GetFirstArgumentAsBytes(info, max_length, thrower, &is_shared);
if (bytes.empty()) {
return {};
}
// Use relaxed reads (and writes, which is unnecessary here) to avoid TSan
// reports in case the buffer is shared and is being modified concurrently.
auto result = base::OwnedVector<uint8_t>::NewForOverwrite(bytes.size());
base::Relaxed_Memcpy(reinterpret_cast<base::Atomic8*>(result.begin()),
reinterpret_cast<const base::Atomic8*>(bytes.data()),
bytes.size());
return result;
}
namespace {
i::MaybeDirectHandle<i::JSReceiver> ImportsAsMaybeReceiver(Local<Value> ffi) {
if (ffi->IsUndefined()) return {};
Local<Object> obj = Local<Object>::Cast(ffi);
return i::Cast<i::JSReceiver>(v8::Utils::OpenDirectHandle(*obj));
}
// 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(Isolate* isolate, Local<Context> context,
Local<Promise::Resolver> promise_resolver)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver) {
context_.SetWeak();
promise_resolver_.AnnotateStrongRetainer(kGlobalPromiseHandle);
}
void OnCompilationSucceeded(
i::DirectHandle<i::WasmModuleObject> result) override {
if (finished_) return;
finished_ = true;
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(i::Cast<i::Object>(result)),
WasmAsyncSuccess::kSuccess);
}
void OnCompilationFailed(i::DirectHandle<i::Object> error_reason) override {
if (finished_) return;
finished_ = true;
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(error_reason), WasmAsyncSuccess::kFail);
}
private:
static constexpr char kGlobalPromiseHandle[] =
"AsyncCompilationResolver::promise_";
bool finished_ = false;
Isolate* isolate_;
Global<Context> context_;
Global<Promise::Resolver> promise_resolver_;
};
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(Isolate* isolate, Local<Context> context,
Local<Promise::Resolver> promise_resolver)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver) {
context_.SetWeak();
promise_resolver_.AnnotateStrongRetainer(kGlobalPromiseHandle);
}
void OnInstantiationSucceeded(
i::DirectHandle<i::WasmInstanceObject> instance) override {
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(i::Cast<i::Object>(instance)),
WasmAsyncSuccess::kSuccess);
}
void OnInstantiationFailed(i::DirectHandle<i::Object> error_reason) override {
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(error_reason), WasmAsyncSuccess::kFail);
}
private:
static constexpr char kGlobalPromiseHandle[] =
"InstantiateModuleResultResolver::promise_";
Isolate* isolate_;
Global<Context> context_;
Global<Promise::Resolver> promise_resolver_;
};
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(Isolate* isolate, Local<Context> context,
Local<Promise::Resolver> promise_resolver,
Local<Value> module)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver),
module_(isolate, module) {
context_.SetWeak();
promise_resolver_.AnnotateStrongRetainer(kGlobalPromiseHandle);
module_.AnnotateStrongRetainer(kGlobalModuleHandle);
}
void OnInstantiationSucceeded(
i::DirectHandle<i::WasmInstanceObject> instance) override {
if (context_.IsEmpty()) return;
Local<Context> context = context_.Get(isolate_);
WasmAsyncSuccess success = WasmAsyncSuccess::kSuccess;
// The result is a JSObject with 2 fields which contain the
// WasmInstanceObject and the WasmModuleObject.
Local<Object> result = Object::New(isolate_);
if (V8_UNLIKELY(result
->CreateDataProperty(context,
v8_str(isolate_, "module"),
module_.Get(isolate_))
.IsNothing())) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
// We assume that a TerminationException is the only reason why
// `CreateDataProperty` can fail here. We should revisit
// https://crbug.com/1515227 again if this CHECK fails.
CHECK(i::IsTerminationException(i_isolate->exception()));
result = Utils::ToLocal(direct_handle(
i::Cast<i::JSObject>(i_isolate->exception()), i_isolate));
success = WasmAsyncSuccess::kFail;
}
if (V8_UNLIKELY(result
->CreateDataProperty(
context, v8_str(isolate_, "instance"),
Utils::ToLocal(i::Cast<i::Object>(instance)))
.IsNothing())) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate_);
CHECK(i::IsTerminationException(i_isolate->exception()));
result = Utils::ToLocal(direct_handle(
i::Cast<i::JSObject>(i_isolate->exception()), i_isolate));
success = WasmAsyncSuccess::kFail;
}
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context, promise_resolver_.Get(isolate_), result,
success);
}
void OnInstantiationFailed(i::DirectHandle<i::Object> error_reason) override {
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(error_reason), WasmAsyncSuccess::kFail);
}
private:
static constexpr char kGlobalPromiseHandle[] =
"InstantiateBytesResultResolver::promise_";
static constexpr char kGlobalModuleHandle[] =
"InstantiateBytesResultResolver::module_";
Isolate* isolate_;
Global<Context> context_;
Global<Promise::Resolver> promise_resolver_;
Global<Value> 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(
Isolate* isolate, Local<Context> context,
Local<Promise::Resolver> promise_resolver, Local<Value> imports)
: isolate_(isolate),
context_(isolate, context),
promise_resolver_(isolate, promise_resolver),
imports_(isolate, imports) {
context_.SetWeak();
promise_resolver_.AnnotateStrongRetainer(kGlobalPromiseHandle);
imports_.AnnotateStrongRetainer(kGlobalImportsHandle);
}
void OnCompilationSucceeded(
i::DirectHandle<i::WasmModuleObject> result) override {
if (finished_) return;
finished_ = true;
i::wasm::GetWasmEngine()->AsyncInstantiate(
reinterpret_cast<i::Isolate*>(isolate_),
std::make_unique<InstantiateBytesResultResolver>(
isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(i::Cast<i::Object>(result))),
result, ImportsAsMaybeReceiver(imports_.Get(isolate_)));
}
void OnCompilationFailed(i::DirectHandle<i::Object> error_reason) override {
if (finished_) return;
finished_ = true;
if (context_.IsEmpty()) return;
auto callback = reinterpret_cast<i::Isolate*>(isolate_)
->wasm_async_resolve_promise_callback();
CHECK(callback);
callback(isolate_, context_.Get(isolate_), promise_resolver_.Get(isolate_),
Utils::ToLocal(error_reason), WasmAsyncSuccess::kFail);
}
private:
static constexpr char kGlobalPromiseHandle[] =
"AsyncInstantiateCompileResultResolver::promise_";
static constexpr char kGlobalImportsHandle[] =
"AsyncInstantiateCompileResultResolver::module_";
bool finished_ = false;
Isolate* isolate_;
Global<Context> context_;
Global<Promise::Resolver> promise_resolver_;
Global<Value> imports_;
};
constexpr char AsyncInstantiateCompileResultResolver::kGlobalPromiseHandle[];
constexpr char AsyncInstantiateCompileResultResolver::kGlobalImportsHandle[];
// TODO(clemensb): Make this less inefficient.
std::string ToString(const char* name) { return std::string(name); }
std::string ToString(const i::DirectHandle<i::String> name) {
return std::string("Property '") + name->ToCString().get() + "'";
}
// Web IDL: '[EnforceRange] unsigned long'
// https://heycam.github.io/webidl/#EnforceRange
template <typename Name>
std::optional<uint32_t> EnforceUint32(Name argument_name, Local<v8::Value> v,
Local<Context> context,
ErrorThrower* thrower) {
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 std::nullopt;
}
if (!std::isfinite(double_number)) {
thrower->TypeError("%s must be convertible to a valid number",
ToString(argument_name).c_str());
return std::nullopt;
}
if (double_number < 0) {
thrower->TypeError("%s must be non-negative",
ToString(argument_name).c_str());
return std::nullopt;
}
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 std::nullopt;
}
return static_cast<uint32_t>(double_number);
}
// First step of AddressValueToU64, for addrtype == "i64".
template <typename Name>
std::optional<uint64_t> EnforceBigIntUint64(Name argument_name, Local<Value> v,
Local<Context> context,
ErrorThrower* thrower) {
// Use the internal API, as v8::Value::ToBigInt clears exceptions.
i::DirectHandle<i::BigInt> bigint;
i::Isolate* i_isolate = i::Isolate::Current();
if (!i::BigInt::FromObject(i_isolate, Utils::OpenDirectHandle(*v))
.ToHandle(&bigint)) {
return std::nullopt;
}
bool lossless;
uint64_t result = bigint->AsUint64(&lossless);
if (!lossless) {
thrower->TypeError("%s must be in u64 range",
ToString(argument_name).c_str());
return std::nullopt;
}
return result;
}
// The enum values need to match "WasmCompilationMethod" in
// tools/metrics/histograms/enums.xml.
enum CompilationMethod {
kSyncCompilation = 0,
kAsyncCompilation = 1,
kStreamingCompilation = 2,
kAsyncInstantiation = 3,
kStreamingInstantiation = 4,
};
void RecordCompilationMethod(i::Isolate* isolate, CompilationMethod method) {
isolate->counters()->wasm_compilation_method()->AddSample(method);
}
CompileTimeImports ArgumentToCompileOptions(
Local<Value> arg_value, i::Isolate* isolate,
WasmEnabledFeatures enabled_features) {
CompileTimeImports result;
if (base::FPU::GetFlushDenormals()) {
result.Add(CompileTimeImport::kDisableDenormalFloats);
}
if (!enabled_features.has_imported_strings()) return result;
i::DirectHandle<i::Object> arg = Utils::OpenDirectHandle(*arg_value);
if (!i::IsJSReceiver(*arg)) return result;
i::DirectHandle<i::JSReceiver> receiver = i::Cast<i::JSReceiver>(arg);
// ==================== Builtins ====================
i::DirectHandle<i::JSAny> builtins;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, builtins,
i::Cast<i::JSAny>(i::JSReceiver::GetProperty(
isolate, receiver, "builtins")),
{});
if (i::IsJSReceiver(*builtins)) {
i::DirectHandle<i::Object> length_obj;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, length_obj,
i::Object::GetLengthFromArrayLike(isolate,
i::Cast<i::JSReceiver>(builtins)),
{});
double raw_length = i::Object::NumberValue(*length_obj);
// Technically we should probably iterate up to 2^53-1 if {length_obj} says
// so, but lengths above 2^32 probably don't happen in practice (and would
// be very slow if they do), so just use a saturating to-uint32 conversion
// for simplicity.
uint32_t len = raw_length >= i::kMaxUInt32
? i::kMaxUInt32
: static_cast<uint32_t>(raw_length);
for (uint32_t i = 0; i < len; i++) {
i::LookupIterator it(isolate, builtins, i);
Maybe<bool> maybe_found = i::JSReceiver::HasProperty(&it);
MAYBE_RETURN(maybe_found, {});
if (!maybe_found.FromJust()) continue;
i::DirectHandle<i::Object> value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, value,
i::Object::GetProperty(&it), {});
if (i::IsString(*value)) {
i::Tagged<i::String> builtin = i::Cast<i::String>(*value);
// TODO(jkummerow): We could make other string comparisons to known
// constants in this file more efficient by migrating them to this
// style (rather than `...->StringEquals(v8_str(...))`).
if (builtin->IsEqualTo(base::CStrVector("js-string"))) {
result.Add(CompileTimeImport::kJsString);
continue;
}
if (enabled_features.has_imported_strings_utf8()) {
if (builtin->IsEqualTo(base::CStrVector("text-encoder"))) {
result.Add(CompileTimeImport::kTextEncoder);
continue;
}
if (builtin->IsEqualTo(base::CStrVector("text-decoder"))) {
result.Add(CompileTimeImport::kTextDecoder);
continue;
}
}
}
}
}
// ==================== String constants ====================
i::DirectHandle<i::String> importedStringConstants =
isolate->factory()->InternalizeUtf8String("importedStringConstants");
if (i::JSReceiver::HasProperty(isolate, receiver, importedStringConstants)
.FromMaybe(false)) {
i::DirectHandle<i::Object> constants_value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, constants_value,
i::JSReceiver::GetProperty(isolate, receiver, importedStringConstants),
{});
if (i::IsString(*constants_value)) {
i::ToUtf8Lossy(isolate, i::Cast<i::String>(constants_value),
result.constants_module());
result.Add(CompileTimeImport::kStringConstants);
}
}
return result;
}
// A scope object with accessors and destructur DCHECKs to be used in
// implementations of Wasm JS-API methods.
class WasmJSApiScope {
public:
explicit WasmJSApiScope(
const v8::FunctionCallbackInfo<v8::Value>& callback_info,
const char* api_name)
: callback_info_(callback_info),
isolate_{callback_info.GetIsolate()},
handle_scope_{isolate_},
thrower_{reinterpret_cast<i::Isolate*>(isolate_), api_name} {
DCHECK(i::ValidateCallbackInfo(callback_info));
}
WasmJSApiScope(const WasmJSApiScope&) = delete;
WasmJSApiScope& operator=(const WasmJSApiScope&) = delete;
#if DEBUG
~WasmJSApiScope() {
// If there was an exception we should not have a return value set.
DCHECK_IMPLIES(i_isolate()->has_exception() || thrower_.error(),
callback_info_.GetReturnValue().Get()->IsUndefined());
}
#endif
void AssertException() const {
DCHECK(callback_info_.GetReturnValue().Get()->IsUndefined());
DCHECK(i_isolate()->has_exception() || thrower_.error());
}
const v8::FunctionCallbackInfo<v8::Value>& callback_info() {
return callback_info_;
}
const char* api_name() const { return thrower_.context_name(); }
// Accessor for all essential fields. To be decomposed into individual aliases
// via structured binding.
std::tuple<v8::Isolate*, i::Isolate*, ErrorThrower&> isolates_and_thrower() {
return {isolate_, i_isolate(), thrower_};
}
private:
i::Isolate* i_isolate() const {
return reinterpret_cast<i::Isolate*>(isolate_);
}
const v8::FunctionCallbackInfo<v8::Value>& callback_info_;
v8::Isolate* const isolate_;
HandleScope handle_scope_;
ErrorThrower thrower_;
};
} // namespace
// WebAssembly.compile(bytes, options) -> Promise
void WebAssemblyCompileImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.compile()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
RecordCompilationMethod(i_isolate, kAsyncCompilation);
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 = info.GetReturnValue();
return_value.Set(promise);
std::shared_ptr<i::wasm::CompilationResultResolver> resolver(
new AsyncCompilationResolver(isolate, context, promise_resolver));
i::DirectHandle<i::NativeContext> native_context =
i_isolate->native_context();
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, native_context)) {
i::DirectHandle<i::String> error =
i::wasm::ErrorStringForCodegen(i_isolate, native_context);
thrower.CompileError("%s", error->ToCString().get());
resolver->OnCompilationFailed(thrower.Reify());
return;
}
base::OwnedVector<const uint8_t> bytes = GetAndCopyFirstArgumentAsBytes(
info, i::wasm::max_module_size(), &thrower);
if (bytes.empty()) {
resolver->OnCompilationFailed(thrower.Reify());
return;
}
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
CompileTimeImports compile_imports =
ArgumentToCompileOptions(info[1], i_isolate, enabled_features);
if (i_isolate->has_exception()) {
if (i_isolate->is_execution_terminating()) return;
resolver->OnCompilationFailed(
direct_handle(i_isolate->exception(), i_isolate));
i_isolate->clear_exception();
return;
}
i::wasm::GetWasmEngine()->AsyncCompile(
i_isolate, enabled_features, std::move(compile_imports),
std::move(resolver), std::move(bytes), js_api_scope.api_name());
}
void WasmStreamingCallbackForTesting(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.compile()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
std::shared_ptr<v8::WasmStreaming> streaming =
v8::WasmStreaming::Unpack(info.GetIsolate(), info.Data());
// We don't check the buffer length up front, to allow d8 to test that the
// streaming decoder implementation handles overly large inputs correctly.
size_t unlimited = std::numeric_limits<size_t>::max();
base::OwnedVector<const uint8_t> bytes =
GetAndCopyFirstArgumentAsBytes(info, unlimited, &thrower);
if (bytes.empty()) {
streaming->Abort(Utils::ToLocal(thrower.Reify()));
return;
}
streaming->OnBytesReceived(bytes.begin(), bytes.size());
streaming->Finish();
CHECK(!thrower.error());
}
void WasmStreamingPromiseFailedCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
DCHECK(i::ValidateCallbackInfo(info));
std::shared_ptr<v8::WasmStreaming> streaming =
v8::WasmStreaming::Unpack(info.GetIsolate(), info.Data());
streaming->Abort(info[0]);
}
void StartAsyncCompilationWithResolver(
WasmJSApiScope& js_api_scope, Local<Value> response_or_promise,
Local<Value> options_arg_value,
std::shared_ptr<i::wasm::CompilationResultResolver> resolver,
ReturnValue<Value> return_value) {
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
Local<Context> context = isolate->GetCurrentContext();
i::DirectHandle<i::NativeContext> native_context =
Utils::OpenHandle(*context);
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, native_context)) {
i::DirectHandle<i::String> error =
i::wasm::ErrorStringForCodegen(i_isolate, native_context);
thrower.CompileError("%s", error->ToCString().get());
resolver->OnCompilationFailed(thrower.Reify());
return;
}
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
CompileTimeImports compile_imports =
ArgumentToCompileOptions(options_arg_value, i_isolate, enabled_features);
if (i_isolate->has_exception()) {
if (i_isolate->is_execution_terminating()) return;
resolver->OnCompilationFailed(
direct_handle(i_isolate->exception(), i_isolate));
i_isolate->clear_exception();
return;
}
// Allocate the streaming decoder in a Managed so we can pass it to the
// embedder.
std::shared_ptr<WasmStreaming> streaming = std::make_shared<WasmStreaming>(
std::make_unique<WasmStreaming::WasmStreamingImpl>(
i_isolate, js_api_scope.api_name(), std::move(compile_imports),
resolver));
i::DirectHandle<i::Managed<WasmStreaming>> data =
i::Managed<WasmStreaming>::From(i_isolate, 0, streaming);
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::Cast<i::Object>(data)), 1));
ASSIGN(v8::Function, reject_callback,
v8::Function::New(context, WasmStreamingPromiseFailedCallback,
Utils::ToLocal(i::Cast<i::Object>(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, response_or_promise).IsJust()) return;
// Calling `then` on the promise can fail if the user monkey-patched stuff,
// see https://crbug.com/374820218 / https://crbug.com/396461004.
// If this does not fail, then the {compile_callback} will start streaming
// compilation, which will eventually resolve the promise we set as result
// value.
if (input_resolver->GetPromise()
->Then(context, compile_callback, reject_callback)
.IsEmpty()) {
streaming->Abort(MaybeLocal<Value>{});
return_value.SetUndefined();
return js_api_scope.AssertException();
}
}
// WebAssembly.compileStreaming(Response | Promise<Response>, options)
// -> Promise<WebAssembly.Module>
void WebAssemblyCompileStreaming(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.compileStreaming()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
RecordCompilationMethod(i_isolate, kStreamingCompilation);
Local<Context> context = isolate->GetCurrentContext();
// Create and assign the return value of this function.
ASSIGN(Promise::Resolver, promise_resolver, Promise::Resolver::New(context));
Local<Promise> promise = promise_resolver->GetPromise();
info.GetReturnValue().Set(promise);
// Prepare the CompilationResultResolver for the compilation.
auto resolver = std::make_shared<AsyncCompilationResolver>(isolate, context,
promise_resolver);
StartAsyncCompilationWithResolver(js_api_scope, info[0], info[1], resolver,
info.GetReturnValue());
}
// WebAssembly.validate(bytes, options) -> bool
void WebAssemblyValidateImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.validate()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
bool bytes_are_shared = false;
base::Vector<const uint8_t> bytes = GetFirstArgumentAsBytes(
info, i::wasm::max_module_size(), &thrower, &bytes_are_shared);
if (bytes.empty()) {
js_api_scope.AssertException();
// Propagate anything except wasm exceptions.
if (!thrower.wasm_error()) return;
// Clear wasm exceptions; return false instead.
thrower.Reset();
return_value.Set(v8::False(isolate));
return;
}
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
CompileTimeImports compile_imports =
ArgumentToCompileOptions(info[1], i_isolate, enabled_features);
if (i_isolate->has_exception()) {
if (i_isolate->is_execution_terminating()) return;
i_isolate->clear_exception();
return_value.Set(v8::False(isolate));
return;
}
bool validated = false;
if (bytes_are_shared) {
// Make a copy of the wire bytes to avoid concurrent modification.
// Use relaxed reads (and writes, which is unnecessary here) to avoid TSan
// reports in case the buffer is shared and is being modified concurrently.
auto bytes_copy = base::OwnedVector<uint8_t>::NewForOverwrite(bytes.size());
base::Relaxed_Memcpy(reinterpret_cast<base::Atomic8*>(bytes_copy.begin()),
reinterpret_cast<const base::Atomic8*>(bytes.data()),
bytes.size());
validated = i::wasm::GetWasmEngine()->SyncValidate(
i_isolate, enabled_features, std::move(compile_imports),
bytes_copy.as_vector());
} else {
// The wire bytes are not shared, OK to use them directly.
validated = i::wasm::GetWasmEngine()->SyncValidate(
i_isolate, enabled_features, std::move(compile_imports), bytes);
}
return_value.Set(validated);
}
namespace {
bool TransferPrototype(i::Isolate* isolate,
i::DirectHandle<i::JSObject> destination,
i::DirectHandle<i::JSReceiver> source) {
i::MaybeDirectHandle<i::HeapObject> maybe_prototype =
i::JSObject::GetPrototype(isolate, source);
i::DirectHandle<i::HeapObject> prototype;
if (maybe_prototype.ToHandle(&prototype)) {
Maybe<bool> result = i::JSObject::SetPrototype(
isolate, destination, prototype,
/*from_javascript=*/false, internal::kThrowOnError);
if (!result.FromJust()) {
DCHECK(isolate->has_exception());
return false;
}
}
return true;
}
} // namespace
// new WebAssembly.Module(bytes, options) -> WebAssembly.Module
void WebAssemblyModuleImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Module()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (i_isolate->wasm_module_callback()(info)) return;
RecordCompilationMethod(i_isolate, kSyncCompilation);
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Module must be invoked with 'new'");
return;
}
i::DirectHandle<i::NativeContext> native_context =
i_isolate->native_context();
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, native_context)) {
i::DirectHandle<i::String> error =
i::wasm::ErrorStringForCodegen(i_isolate, native_context);
thrower.CompileError("%s", error->ToCString().get());
return;
}
base::OwnedVector<const uint8_t> bytes = GetAndCopyFirstArgumentAsBytes(
info, i::wasm::max_module_size(), &thrower);
if (bytes.empty()) return js_api_scope.AssertException();
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
CompileTimeImports compile_imports =
ArgumentToCompileOptions(info[1], i_isolate, enabled_features);
if (i_isolate->has_exception()) {
// TODO(14179): Does this need different error message handling?
return;
}
i::MaybeDirectHandle<i::WasmModuleObject> maybe_module_obj;
maybe_module_obj = i::wasm::GetWasmEngine()->SyncCompile(
i_isolate, enabled_features, std::move(compile_imports), &thrower,
std::move(bytes));
i::DirectHandle<i::WasmModuleObject> module_obj;
if (!maybe_module_obj.ToHandle(&module_obj)) return;
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {module_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Module} directly, but some
// subclass: {module_obj} has {WebAssembly.Module}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, module_obj,
Utils::OpenDirectHandle(*info.This()))) {
return;
}
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
return_value.Set(Utils::ToLocal(module_obj));
}
// WebAssembly.Module.imports(module) -> Array<Import>
void WebAssemblyModuleImportsImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Module.imports()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i::DirectHandle<i::WasmModuleObject> module_object;
if (!GetFirstArgumentAsModule(info, &thrower).ToHandle(&module_object)) {
return js_api_scope.AssertException();
}
auto imports = i::wasm::GetImports(i_isolate, module_object);
info.GetReturnValue().Set(Utils::ToLocal(imports));
}
// WebAssembly.Module.exports(module) -> Array<Export>
void WebAssemblyModuleExportsImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Module.exports()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i::DirectHandle<i::WasmModuleObject> module_object;
if (!GetFirstArgumentAsModule(info, &thrower).ToHandle(&module_object)) {
return js_api_scope.AssertException();
}
auto exports = i::wasm::GetExports(i_isolate, module_object);
info.GetReturnValue().Set(Utils::ToLocal(exports));
}
// WebAssembly.Module.customSections(module, name) -> Array<Section>
void WebAssemblyModuleCustomSectionsImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Module.customSections()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i::DirectHandle<i::WasmModuleObject> module_object;
if (!GetFirstArgumentAsModule(info, &thrower).ToHandle(&module_object)) {
return js_api_scope.AssertException();
}
if (info[1]->IsUndefined()) {
thrower.TypeError("Argument 1 is required");
return;
}
i::DirectHandle<i::Object> name;
if (!i::Object::ToString(i_isolate, Utils::OpenDirectHandle(*info[1]))
.ToHandle(&name)) {
return js_api_scope.AssertException();
}
auto custom_sections = i::wasm::GetCustomSections(
i_isolate, module_object, i::Cast<i::String>(name), &thrower);
if (thrower.error()) return;
info.GetReturnValue().Set(Utils::ToLocal(custom_sections));
}
// new WebAssembly.Instance(module, imports) -> WebAssembly.Instance
void WebAssemblyInstanceImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Instance()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
RecordCompilationMethod(i_isolate, kAsyncInstantiation);
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
if (i_isolate->wasm_instance_callback()(info)) return;
i::DirectHandle<i::JSObject> instance_obj;
{
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Instance must be invoked with 'new'");
return;
}
i::DirectHandle<i::WasmModuleObject> module_object;
if (!GetFirstArgumentAsModule(info, &thrower).ToHandle(&module_object)) {
return js_api_scope.AssertException();
}
Local<Value> ffi = info[1];
if (!ffi->IsUndefined() && !ffi->IsObject()) {
thrower.TypeError("Argument 1 must be an object");
return;
}
if (!i::wasm::GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, module_object,
ImportsAsMaybeReceiver(ffi),
i::MaybeDirectHandle<i::JSArrayBuffer>())
.ToHandle(&instance_obj)) {
return js_api_scope.AssertException();
}
}
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {instance_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Instance} directly, but some
// subclass: {instance_obj} has {WebAssembly.Instance}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, instance_obj,
Utils::OpenDirectHandle(*info.This()))) {
return js_api_scope.AssertException();
}
info.GetReturnValue().Set(Utils::ToLocal(instance_obj));
}
// WebAssembly.instantiateStreaming(
// Response | Promise<Response> [, imports [, options]])
// -> Promise<ResultObject>
// (where ResultObject has a "module" and an "instance" field)
void WebAssemblyInstantiateStreaming(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.instantiateStreaming()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
RecordCompilationMethod(i_isolate, kStreamingInstantiation);
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
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();
info.GetReturnValue().Set(promise);
// If info.Length < 2, this will be undefined - see FunctionCallbackInfo.
Local<Value> ffi = info[1];
if (!ffi->IsUndefined() && !ffi->IsObject()) {
thrower.TypeError("Argument 1 must be an object");
InstantiateModuleResultResolver resolver(isolate, context, result_resolver);
resolver.OnInstantiationFailed(thrower.Reify());
return;
}
auto compilation_resolver =
std::make_shared<AsyncInstantiateCompileResultResolver>(
isolate, context, result_resolver, ffi);
StartAsyncCompilationWithResolver(js_api_scope, info[0], info[2],
std::move(compilation_resolver),
info.GetReturnValue());
}
// WebAssembly.instantiate(module, imports) -> WebAssembly.Instance
// WebAssembly.instantiate(bytes, imports, options) ->
// {module: WebAssembly.Module, instance: WebAssembly.Instance}
void WebAssemblyInstantiateImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.instantiate()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i_isolate->CountUsage(
v8::Isolate::UseCounterFeature::kWebAssemblyInstantiation);
Local<Context> context = isolate->GetCurrentContext();
ASSIGN(Promise::Resolver, promise_resolver, Promise::Resolver::New(context));
Local<Promise> promise = promise_resolver->GetPromise();
info.GetReturnValue().Set(promise);
std::unique_ptr<i::wasm::InstantiationResultResolver> resolver(
new InstantiateModuleResultResolver(isolate, context, promise_resolver));
Local<Value> first_arg_value = info[0];
i::DirectHandle<i::Object> first_arg =
Utils::OpenDirectHandle(*first_arg_value);
if (!IsJSObject(*first_arg)) {
thrower.TypeError(
"Argument 0 must be a buffer source or a WebAssembly.Module object");
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
// If info.Length < 2, this will be undefined - see FunctionCallbackInfo.
Local<Value> ffi = info[1];
if (!ffi->IsUndefined() && !ffi->IsObject()) {
thrower.TypeError("Argument 1 must be an object");
resolver->OnInstantiationFailed(thrower.Reify());
return;
}
if (IsWasmModuleObject(*first_arg)) {
i::DirectHandle<i::WasmModuleObject> module_obj =
i::Cast<i::WasmModuleObject>(first_arg);
i::wasm::GetWasmEngine()->AsyncInstantiate(i_isolate, std::move(resolver),
module_obj,
ImportsAsMaybeReceiver(ffi));
return;
}
base::OwnedVector<const uint8_t> bytes = GetAndCopyFirstArgumentAsBytes(
info, i::wasm::max_module_size(), &thrower);
if (bytes.empty()) {
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(isolate, context,
promise_resolver, ffi));
// The first parameter is a buffer source, we have to check if we are allowed
// to compile it.
i::DirectHandle<i::NativeContext> native_context =
i_isolate->native_context();
if (!i::wasm::IsWasmCodegenAllowed(i_isolate, native_context)) {
i::DirectHandle<i::String> error =
i::wasm::ErrorStringForCodegen(i_isolate, native_context);
thrower.CompileError("%s", error->ToCString().get());
compilation_resolver->OnCompilationFailed(thrower.Reify());
return;
}
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
CompileTimeImports compile_imports =
ArgumentToCompileOptions(info[2], i_isolate, enabled_features);
if (i_isolate->has_exception()) {
if (i_isolate->is_execution_terminating()) return;
compilation_resolver->OnCompilationFailed(
direct_handle(i_isolate->exception(), i_isolate));
i_isolate->clear_exception();
return;
}
i::wasm::GetWasmEngine()->AsyncCompile(
i_isolate, enabled_features, std::move(compile_imports),
std::move(compilation_resolver), std::move(bytes),
js_api_scope.api_name());
}
namespace {
// {AddressValueToU64} as defined in the memory64 js-api spec.
// Returns std::nullopt on error (exception or error set in the thrower), and
// the address value otherwise.
template <typename Name>
std::optional<uint64_t> AddressValueToU64(ErrorThrower* thrower,
Local<Context> context,
v8::Local<v8::Value> value,
Name property_name,
AddressType address_type) {
switch (address_type) {
case AddressType::kI32:
return EnforceUint32(property_name, value, context, thrower);
case AddressType::kI64:
return EnforceBigIntUint64(property_name, value, context, thrower);
}
// The enum value is coming from inside the sandbox and while the switch is
// exhaustive, it's not guaranteed that value is one of the declared values.
SBXCHECK(false);
}
// {AddressValueToU64} plus additional bounds checks.
std::optional<uint64_t> AddressValueToBoundedU64(
ErrorThrower* thrower, Local<Context> context, v8::Local<v8::Value> value,
i::DirectHandle<i::String> property_name, AddressType address_type,
uint64_t lower_bound, uint64_t upper_bound) {
std::optional<uint64_t> maybe_address_value =
AddressValueToU64(thrower, context, value, property_name, address_type);
if (!maybe_address_value) return std::nullopt;
uint64_t address_value = *maybe_address_value;
if (address_value < lower_bound) {
thrower->RangeError(
"Property '%s': value %" PRIu64 " is below the lower bound %" PRIx64,
property_name->ToCString().get(), address_value, lower_bound);
return std::nullopt;
}
if (address_value > upper_bound) {
thrower->RangeError(
"Property '%s': value %" PRIu64 " is above the upper bound %" PRIu64,
property_name->ToCString().get(), address_value, upper_bound);
return std::nullopt;
}
return address_value;
}
// Returns std::nullopt on error (exception or error set in the thrower).
// The inner optional is std::nullopt if the property did not exist, and the
// address value otherwise.
std::optional<std::optional<uint64_t>> GetOptionalAddressValue(
ErrorThrower* thrower, Local<Context> context, Local<v8::Object> descriptor,
Local<String> property, AddressType address_type, int64_t lower_bound,
uint64_t upper_bound) {
v8::Local<v8::Value> value;
if (!descriptor->Get(context, property).ToLocal(&value)) {
return std::nullopt;
}
// Web IDL: dictionary presence
// https://heycam.github.io/webidl/#dfn-present
if (value->IsUndefined()) {
// No exception, but no value either.
return std::optional<uint64_t>{};
}
i::DirectHandle<i::String> property_name =
v8::Utils::OpenDirectHandle(*property);
std::optional<uint64_t> maybe_address_value =
AddressValueToBoundedU64(thrower, context, value, property_name,
address_type, lower_bound, upper_bound);
if (!maybe_address_value) return std::nullopt;
return *maybe_address_value;
}
// Fetch 'initial' or 'minimum' property from `descriptor`. If both are
// provided, a TypeError is thrown.
// Returns std::nullopt on error (exception or error set in the thrower).
// TODO(aseemgarg): change behavior when the following bug is resolved:
// https://github.com/WebAssembly/js-types/issues/6
std::optional<uint64_t> GetInitialOrMinimumProperty(
v8::Isolate* isolate, ErrorThrower* thrower, Local<Context> context,
Local<v8::Object> descriptor, AddressType address_type,
uint64_t upper_bound) {
auto maybe_maybe_initial = GetOptionalAddressValue(
thrower, context, descriptor, v8_str(isolate, "initial"), address_type, 0,
upper_bound);
if (!maybe_maybe_initial) return std::nullopt;
std::optional<uint64_t> maybe_initial = *maybe_maybe_initial;
auto enabled_features =
WasmEnabledFeatures::FromIsolate(reinterpret_cast<i::Isolate*>(isolate));
if (enabled_features.has_type_reflection()) {
auto maybe_maybe_minimum = GetOptionalAddressValue(
thrower, context, descriptor, v8_str(isolate, "minimum"), address_type,
0, upper_bound);
if (!maybe_maybe_minimum) return std::nullopt;
std::optional<uint64_t> maybe_minimum = *maybe_maybe_minimum;
if (maybe_initial && maybe_minimum) {
thrower->TypeError(
"The properties 'initial' and 'minimum' are not allowed at the same "
"time");
return std::nullopt;
}
if (maybe_minimum) {
// Only 'minimum' exists, so we use 'minimum' as 'initial'.
return *maybe_minimum;
}
}
if (!maybe_initial) {
// TODO(aseemgarg): update error message when the spec issue is resolved.
thrower->TypeError("Property 'initial' is required");
return std::nullopt;
}
return *maybe_initial;
}
v8::Local<Value> AddressValueFromUnsigned(Isolate* isolate,
i::wasm::AddressType type,
unsigned value) {
return type == i::wasm::AddressType::kI64
? BigInt::NewFromUnsigned(isolate, value).As<Value>()
: Integer::NewFromUnsigned(isolate, value).As<Value>();
}
i::DirectHandle<i::HeapObject> DefaultReferenceValue(i::Isolate* isolate,
i::wasm::ValueType type) {
DCHECK(type.is_object_reference());
// Use undefined for JS type (externref) but null for wasm types as wasm does
// not know undefined.
if (type.heap_representation() == i::wasm::HeapType::kExtern) {
return isolate->factory()->undefined_value();
} else if (!type.use_wasm_null()) {
return isolate->factory()->null_value();
}
return isolate->factory()->wasm_null();
}
// Read the address type from a Memory or Table descriptor.
std::optional<AddressType> GetAddressType(Isolate* isolate,
Local<Context> context,
Local<v8::Object> descriptor,
ErrorThrower* thrower) {
v8::Local<v8::Value> address_value;
if (!descriptor->Get(context, v8_str(isolate, "address"))
.ToLocal(&address_value)) {
return std::nullopt;
}
if (address_value->IsUndefined()) return AddressType::kI32;
i::DirectHandle<i::String> address;
if (!i::Object::ToString(reinterpret_cast<i::Isolate*>(isolate),
Utils::OpenDirectHandle(*address_value))
.ToHandle(&address)) {
return std::nullopt;
}
if (address->IsEqualTo(base::CStrVector("i64"))) return AddressType::kI64;
if (address->IsEqualTo(base::CStrVector("i32"))) return AddressType::kI32;
thrower->TypeError("Unknown address type '%s'; pass 'i32' or 'i64'",
address->ToCString().get());
return std::nullopt;
}
} // namespace
// new WebAssembly.Table(descriptor) -> WebAssembly.Table
void WebAssemblyTableImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Table must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a table descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(info[0]);
i::wasm::ValueType type;
// Parse the 'element' property of the `descriptor`.
{
v8::Local<v8::Value> value;
if (!descriptor->Get(context, v8_str(isolate, "element")).ToLocal(&value)) {
return js_api_scope.AssertException();
}
i::DirectHandle<i::String> string;
if (!i::Object::ToString(reinterpret_cast<i::Isolate*>(isolate),
Utils::OpenDirectHandle(*value))
.ToHandle(&string)) {
return js_api_scope.AssertException();
}
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
// The JS api uses 'anyfunc' instead of 'funcref'.
if (string->IsEqualTo(base::CStrVector("anyfunc"))) {
type = i::wasm::kWasmFuncRef;
} else if (enabled_features.has_type_reflection() &&
string->IsEqualTo(base::CStrVector("funcref"))) {
// With the type reflection proposal, "funcref" replaces "anyfunc",
// and anyfunc just becomes an alias for "funcref".
type = i::wasm::kWasmFuncRef;
} else if (string->IsEqualTo(base::CStrVector("externref"))) {
type = i::wasm::kWasmExternRef;
} else if (enabled_features.has_stringref() &&
string->IsEqualTo(base::CStrVector("stringref"))) {
type = i::wasm::kWasmStringRef;
} else if (string->IsEqualTo(base::CStrVector("anyref"))) {
type = i::wasm::kWasmAnyRef;
} else if (string->IsEqualTo(base::CStrVector("eqref"))) {
type = i::wasm::kWasmEqRef;
} else if (string->IsEqualTo(base::CStrVector("structref"))) {
type = i::wasm::kWasmStructRef;
} else if (string->IsEqualTo(base::CStrVector("arrayref"))) {
type = i::wasm::kWasmArrayRef;
} else if (string->IsEqualTo(base::CStrVector("i31ref"))) {
type = i::wasm::kWasmI31Ref;
} else {
thrower.TypeError(
"Descriptor property 'element' must be a WebAssembly reference type");
return;
}
// TODO(14616): Support shared types.
}
// Parse the 'address' property of the `descriptor`.
std::optional<AddressType> maybe_address_type =
GetAddressType(isolate, context, descriptor, &thrower);
if (!maybe_address_type) {
DCHECK(i_isolate->has_exception() || thrower.error());
return;
}
AddressType address_type = *maybe_address_type;
// Parse the 'initial' or 'minimum' property of the `descriptor`.
std::optional<uint64_t> maybe_initial = GetInitialOrMinimumProperty(
isolate, &thrower, context, descriptor, address_type,
i::wasm::max_table_init_entries());
if (!maybe_initial) return js_api_scope.AssertException();
static_assert(i::wasm::kV8MaxWasmTableInitEntries <= i::kMaxUInt32);
uint32_t initial = static_cast<uint32_t>(*maybe_initial);
// Parse the 'maximum' property of the `descriptor`.
uint64_t kNoMaximum = i::kMaxUInt64;
auto maybe_maybe_maximum = GetOptionalAddressValue(
&thrower, context, descriptor, v8_str(isolate, "maximum"), address_type,
initial, kNoMaximum);
if (!maybe_maybe_maximum) return js_api_scope.AssertException();
std::optional<uint64_t> maybe_maximum = *maybe_maybe_maximum;
DCHECK(!type.has_index()); // The JS API can't express type indices.
i::wasm::CanonicalValueType canonical_type{type};
i::DirectHandle<i::WasmTableObject> table_obj = i::WasmTableObject::New(
i_isolate, i::DirectHandle<i::WasmTrustedInstanceData>(), type,
canonical_type, initial, maybe_maximum.has_value(),
maybe_maximum.value_or(0) /* note: unused if previous param is false */,
DefaultReferenceValue(i_isolate, type), address_type);
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {table_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Table} directly, but some
// subclass: {table_obj} has {WebAssembly.Table}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, table_obj,
Utils::OpenDirectHandle(*info.This()))) {
return js_api_scope.AssertException();
}
if (initial > 0 && info.Length() >= 2 && !info[1]->IsUndefined()) {
i::DirectHandle<i::Object> element = Utils::OpenDirectHandle(*info[1]);
const char* error_message;
if (!i::WasmTableObject::JSToWasmElement(i_isolate, table_obj, element,
&error_message)
.ToHandle(&element)) {
thrower.TypeError(
"Argument 2 must be undefined or a value of type compatible "
"with the type of the new table: %s.",
error_message);
return;
}
for (uint32_t index = 0; index < static_cast<uint32_t>(initial); ++index) {
i::WasmTableObject::Set(i_isolate, table_obj, index, element);
}
} else if (initial > 0) {
DCHECK_EQ(type, table_obj->unsafe_type());
switch (type.heap_representation()) {
case i::wasm::HeapType::kString:
thrower.TypeError(
"Missing initial value when creating stringref table");
return;
case i::wasm::HeapType::kStringViewWtf8:
thrower.TypeError("stringview_wtf8 has no JS representation");
return;
case i::wasm::HeapType::kStringViewWtf16:
thrower.TypeError("stringview_wtf16 has no JS representation");
return;
case i::wasm::HeapType::kStringViewIter:
thrower.TypeError("stringview_iter has no JS representation");
return;
default:
break;
}
}
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
return_value.Set(Utils::ToLocal(i::Cast<i::JSObject>(table_obj)));
}
// new WebAssembly.Memory(descriptor) -> WebAssembly.Memory
void WebAssemblyMemoryImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Memory()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Memory must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a memory descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(info[0]);
// Parse the 'address' property of the `descriptor`.
std::optional<AddressType> maybe_address_type =
GetAddressType(isolate, context, descriptor, &thrower);
if (!maybe_address_type) return js_api_scope.AssertException();
AddressType address_type = *maybe_address_type;
uint64_t max_supported_pages = address_type == AddressType::kI64
? i::wasm::kSpecMaxMemory64Pages
: i::wasm::kSpecMaxMemory32Pages;
// {max_supported_pages} will actually be in integer range. That's the type
// {WasmMemoryObject::New} uses.
static_assert(i::wasm::kSpecMaxMemory32Pages < i::kMaxInt);
static_assert(i::wasm::kSpecMaxMemory64Pages < i::kMaxInt);
// Parse the 'initial' or 'minimum' property of the `descriptor`.
std::optional<uint64_t> maybe_initial =
GetInitialOrMinimumProperty(isolate, &thrower, context, descriptor,
address_type, max_supported_pages);
if (!maybe_initial) {
return js_api_scope.AssertException();
}
uint64_t initial = *maybe_initial;
// Parse the 'maximum' property of the `descriptor`.
auto maybe_maybe_maximum = GetOptionalAddressValue(
&thrower, context, descriptor, v8_str(isolate, "maximum"), address_type,
initial, max_supported_pages);
if (!maybe_maybe_maximum) {
return js_api_scope.AssertException();
}
std::optional<uint64_t> maybe_maximum = *maybe_maybe_maximum;
// Parse the 'shared' property of the `descriptor`.
v8::Local<v8::Value> value;
if (!descriptor->Get(context, v8_str(isolate, "shared")).ToLocal(&value)) {
return js_api_scope.AssertException();
}
auto shared = value->BooleanValue(isolate) ? i::SharedFlag::kShared
: i::SharedFlag::kNotShared;
// Throw TypeError if shared is true, and the descriptor has no "maximum".
if (shared == i::SharedFlag::kShared && !maybe_maximum.has_value()) {
thrower.TypeError("If shared is true, maximum property should be defined.");
return;
}
i::DirectHandle<i::JSObject> memory_obj;
if (!i::WasmMemoryObject::New(i_isolate, static_cast<int>(initial),
maybe_maximum ? static_cast<int>(*maybe_maximum)
: i::WasmMemoryObject::kNoMaximum,
shared, address_type)
.ToHandle(&memory_obj)) {
thrower.RangeError("could not allocate memory");
return;
}
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {memory_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Memory} directly, but some
// subclass: {memory_obj} has {WebAssembly.Memory}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, memory_obj,
Utils::OpenDirectHandle(*info.This()))) {
return js_api_scope.AssertException();
}
if (shared == i::SharedFlag::kShared) {
i::DirectHandle<i::JSArrayBuffer> buffer(
i::Cast<i::WasmMemoryObject>(memory_obj)->array_buffer(), i_isolate);
Maybe<bool> result =
buffer->SetIntegrityLevel(i_isolate, buffer, i::FROZEN, i::kDontThrow);
if (!result.FromJust()) {
thrower.TypeError(
"Status of setting SetIntegrityLevel of buffer is false.");
return;
}
}
info.GetReturnValue().Set(Utils::ToLocal(memory_obj));
}
// new WebAssembly.MemoryMapDescriptor(size) -> WebAssembly.MemoryMapDescriptor
void WebAssemblyMemoryMapDescriptorImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::v8_flags.experimental_wasm_memory_control);
WasmJSApiScope js_api_scope{info, "WebAssembly.MemoryMapDescriptor()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError(
"WebAssembly.MemoryMapDescriptor must be invoked with 'new'");
return js_api_scope.AssertException();
}
std::optional<uint32_t> size =
EnforceUint32("size", info[0], isolate->GetCurrentContext(), &thrower);
if (!size.has_value()) {
return js_api_scope.AssertException();
}
i::DirectHandle<i::JSObject> descriptor_obj;
if (!i::WasmMemoryMapDescriptor::NewFromAnonymous(i_isolate, size.value())
.ToHandle(&descriptor_obj)) {
thrower.RuntimeError("Failed to create a MemoryMapDescriptor");
return js_api_scope.AssertException();
}
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {memory_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Memory} directly, but some
// subclass: {memory_obj} has {WebAssembly.Memory}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, descriptor_obj,
Utils::OpenDirectHandle(*info.This()))) {
DCHECK(i_isolate->has_exception());
return js_api_scope.AssertException();
}
info.GetReturnValue().Set(Utils::ToLocal(descriptor_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::kWasmVoid} in case the
// type could not be properly recognized.
std::optional<i::wasm::ValueType> GetValueType(
Isolate* isolate, MaybeLocal<Value> maybe, Local<Context> context,
WasmEnabledFeatures enabled_features) {
v8::Local<v8::Value> value;
if (!maybe.ToLocal(&value)) return std::nullopt;
i::DirectHandle<i::String> string;
if (!i::Object::ToString(reinterpret_cast<i::Isolate*>(isolate),
Utils::OpenDirectHandle(*value))
.ToHandle(&string)) {
return std::nullopt;
}
if (string->IsEqualTo(base::CStrVector("i32"))) {
return i::wasm::kWasmI32;
} else if (string->IsEqualTo(base::CStrVector("f32"))) {
return i::wasm::kWasmF32;
} else if (string->IsEqualTo(base::CStrVector("i64"))) {
return i::wasm::kWasmI64;
} else if (string->IsEqualTo(base::CStrVector("f64"))) {
return i::wasm::kWasmF64;
} else if (string->IsEqualTo(base::CStrVector("v128"))) {
return i::wasm::kWasmS128;
} else if (string->IsEqualTo(base::CStrVector("externref"))) {
return i::wasm::kWasmExternRef;
} else if (enabled_features.has_type_reflection() &&
string->IsEqualTo(base::CStrVector("funcref"))) {
// The type reflection proposal renames "anyfunc" to "funcref", and makes
// "anyfunc" an alias of "funcref".
return i::wasm::kWasmFuncRef;
} else if (string->IsEqualTo(base::CStrVector("anyfunc"))) {
// The JS api spec uses 'anyfunc' instead of 'funcref'.
return i::wasm::kWasmFuncRef;
} else if (string->IsEqualTo(base::CStrVector("eqref"))) {
return i::wasm::kWasmEqRef;
} else if (enabled_features.has_stringref() &&
string->IsEqualTo(base::CStrVector("stringref"))) {
return i::wasm::kWasmStringRef;
} else if (string->IsEqualTo(base::CStrVector("anyref"))) {
return i::wasm::kWasmAnyRef;
} else if (string->IsEqualTo(base::CStrVector("structref"))) {
return i::wasm::kWasmStructRef;
} else if (string->IsEqualTo(base::CStrVector("arrayref"))) {
return i::wasm::kWasmArrayRef;
} else if (string->IsEqualTo(base::CStrVector("i31ref"))) {
return i::wasm::kWasmI31Ref;
} else if (enabled_features.has_exnref() &&
string->IsEqualTo(base::CStrVector("exnref"))) {
return i::wasm::kWasmExnRef;
}
// Unrecognized type.
return i::wasm::kWasmVoid;
}
namespace {
bool ToI32(Local<v8::Value> value, Local<Context> context, int32_t* i32_value) {
if (!value->IsUndefined()) {
v8::Local<v8::Int32> int32_value;
if (!value->ToInt32(context).ToLocal(&int32_value)) return false;
if (!int32_value->Int32Value(context).To(i32_value)) return false;
}
return true;
}
bool ToI64(Local<v8::Value> value, Local<Context> context, int64_t* i64_value) {
if (!value->IsUndefined()) {
v8::Local<v8::BigInt> bigint_value;
if (!value->ToBigInt(context).ToLocal(&bigint_value)) return false;
*i64_value = bigint_value->Int64Value();
}
return true;
}
bool ToF32(Local<v8::Value> value, Local<Context> context, float* f32_value) {
if (!value->IsUndefined()) {
double f64_value = 0;
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return false;
if (!number_value->NumberValue(context).To(&f64_value)) return false;
*f32_value = i::DoubleToFloat32(f64_value);
}
return true;
}
bool ToF64(Local<v8::Value> value, Local<Context> context, double* f64_value) {
if (!value->IsUndefined()) {
v8::Local<v8::Number> number_value;
if (!value->ToNumber(context).ToLocal(&number_value)) return false;
if (!number_value->NumberValue(context).To(f64_value)) return false;
}
return true;
}
} // namespace
// WebAssembly.Global
void WebAssemblyGlobalImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Global()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Global must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a global descriptor");
return;
}
Local<Context> context = isolate->GetCurrentContext();
Local<v8::Object> descriptor = Local<Object>::Cast(info[0]);
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
// The descriptor's 'mutable'.
bool is_mutable;
{
v8::Local<v8::Value> value;
if (!descriptor->Get(context, v8_str(isolate, "mutable")).ToLocal(&value)) {
return js_api_scope.AssertException();
}
is_mutable = value->BooleanValue(isolate);
}
// The descriptor's type, called 'value'. It is called 'value' because this
// descriptor is planned to be reused 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"));
std::optional<i::wasm::ValueType> maybe_type =
GetValueType(isolate, maybe, context, enabled_features);
if (!maybe_type) return js_api_scope.AssertException();
type = *maybe_type;
if (type == i::wasm::kWasmVoid) {
thrower.TypeError(
"Descriptor property 'value' must be a WebAssembly type");
return;
}
}
const uint32_t offset = 0;
i::MaybeDirectHandle<i::WasmGlobalObject> maybe_global_obj =
i::WasmGlobalObject::New(
i_isolate, i::DirectHandle<i::WasmTrustedInstanceData>(),
i::MaybeDirectHandle<i::JSArrayBuffer>(),
i::MaybeDirectHandle<i::FixedArray>(), type, offset, is_mutable);
i::DirectHandle<i::WasmGlobalObject> global_obj;
if (!maybe_global_obj.ToHandle(&global_obj)) {
return js_api_scope.AssertException();
}
// The infrastructure for `new Foo` calls allocates an object, which is
// available here as {info.This()}. We're going to discard this object
// and use {global_obj} instead, but it does have the correct prototype,
// which we must harvest from it. This makes a difference when the JS
// constructor function wasn't {WebAssembly.Global} directly, but some
// subclass: {global_obj} has {WebAssembly.Global}'s prototype at this
// point, so we must overwrite that with the correct prototype for {Foo}.
if (!TransferPrototype(i_isolate, global_obj,
Utils::OpenDirectHandle(*info.This()))) {
return js_api_scope.AssertException();
}
// Convert value to a WebAssembly value, the default value is 0.
Local<v8::Value> value = Local<Value>::Cast(info[1]);
switch (type.kind()) {
case i::wasm::kI32: {
int32_t i32_value = 0;
if (!ToI32(value, context, &i32_value)) {
return js_api_scope.AssertException();
}
global_obj->SetI32(i32_value);
break;
}
case i::wasm::kI64: {
int64_t i64_value = 0;
if (!ToI64(value, context, &i64_value)) {
return js_api_scope.AssertException();
}
global_obj->SetI64(i64_value);
break;
}
case i::wasm::kF32: {
float f32_value = 0;
if (!ToF32(value, context, &f32_value)) {
return js_api_scope.AssertException();
}
global_obj->SetF32(f32_value);
break;
}
case i::wasm::kF64: {
double f64_value = 0;
if (!ToF64(value, context, &f64_value)) {
return js_api_scope.AssertException();
}
global_obj->SetF64(f64_value);
break;
}
case i::wasm::kRef:
if (info.Length() < 2) {
thrower.TypeError("Non-defaultable global needs initial value");
return;
}
[[fallthrough]];
case i::wasm::kRefNull: {
// We need the wasm default value {null} over {undefined}.
i::DirectHandle<i::Object> value_handle;
if (info.Length() < 2) {
value_handle = DefaultReferenceValue(i_isolate, type);
} else {
value_handle = Utils::OpenDirectHandle(*value);
const char* error_message;
// While the JS API generally allows indexed types, it currently has
// no way to specify such types in `new WebAssembly.Global(...)`.
// TODO(14034): Fix this if that changes.
DCHECK(!type.has_index());
i::wasm::CanonicalValueType canonical_type{type};
if (!i::wasm::JSToWasmObject(i_isolate, value_handle, canonical_type,
&error_message)
.ToHandle(&value_handle)) {
thrower.TypeError("%s", error_message);
return;
}
}
global_obj->SetRef(value_handle);
break;
}
case i::wasm::kS128: {
thrower.TypeError(
"A global of type 'v128' cannot be created in JavaScript");
return;
}
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kVoid:
case i::wasm::kTop:
case i::wasm::kBottom:
UNREACHABLE();
}
i::DirectHandle<i::JSObject> global_js_object(global_obj);
info.GetReturnValue().Set(Utils::ToLocal(global_js_object));
}
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.Tag
void WebAssemblyTagImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Tag()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Tag must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a tag type");
return;
}
Local<Object> event_type = Local<Object>::Cast(info[0]);
Local<Context> context = isolate->GetCurrentContext();
auto enabled_features = WasmEnabledFeatures::FromIsolate(i_isolate);
// Load the 'parameters' property of the event type.
Local<String> parameters_key = v8_str(isolate, "parameters");
v8::MaybeLocal<v8::Value> parameters_maybe =
event_type->Get(context, parameters_key);
v8::Local<v8::Value> parameters_value;
if (!parameters_maybe.ToLocal(&parameters_value) ||
!parameters_value->IsObject()) {
thrower.TypeError("Argument 0 must be a tag 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;
}
// Decode the tag type and construct a signature.
std::vector<i::wasm::ValueType> param_types(parameters_len,
i::wasm::kWasmVoid);
for (uint32_t i = 0; i < parameters_len; ++i) {
i::wasm::ValueType& type = param_types[i];
MaybeLocal<Value> maybe = parameters->Get(context, i);
std::optional<i::wasm::ValueType> maybe_type =
GetValueType(isolate, maybe, context, enabled_features);
if (!maybe_type) return;
type = *maybe_type;
if (type == i::wasm::kWasmVoid) {
thrower.TypeError(
"Argument 0 parameter type at index #%u must be a value type", i);
return;
}
}
const i::wasm::FunctionSig sig{0, parameters_len, param_types.data()};
// Set the tag index to 0. It is only used for debugging purposes, and has no
// meaningful value when declared outside of a wasm module.
auto tag = i::WasmExceptionTag::New(i_isolate, 0);
i::wasm::CanonicalTypeIndex type_index =
i::wasm::GetWasmEngine()->type_canonicalizer()->AddRecursiveGroup(&sig);
i::DirectHandle<i::JSObject> tag_object =
i::WasmTagObject::New(i_isolate, &sig, type_index, tag,
i::DirectHandle<i::WasmTrustedInstanceData>());
info.GetReturnValue().Set(Utils::ToLocal(tag_object));
}
namespace {
uint32_t GetEncodedSize(i::DirectHandle<i::WasmTagObject> tag_object) {
auto serialized_sig = tag_object->serialized_signature();
i::wasm::WasmTagSig sig{
0, static_cast<size_t>(serialized_sig->length()),
reinterpret_cast<i::wasm::ValueType*>(serialized_sig->begin())};
return i::WasmExceptionPackage::GetEncodedSize(&sig);
}
V8_WARN_UNUSED_RESULT bool EncodeExceptionValues(
v8::Isolate* isolate,
i::DirectHandle<i::PodArray<i::wasm::ValueType>> signature,
i::DirectHandle<i::WasmTagObject> tag_object, const Local<Value>& arg,
ErrorThrower* thrower, i::DirectHandle<i::FixedArray> values_out) {
Local<Context> context = isolate->GetCurrentContext();
uint32_t index = 0;
if (!arg->IsObject()) {
thrower->TypeError("Exception values must be an iterable object");
return false;
}
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
auto values = arg.As<Object>();
uint32_t length = GetIterableLength(i_isolate, context, values);
if (length == i::kMaxUInt32) {
thrower->TypeError("Exception values argument has no length");
return false;
}
if (length != static_cast<uint32_t>(signature->length())) {
thrower->TypeError(
"Number of exception values does not match signature length");
return false;
}
for (int i = 0; i < signature->length(); ++i) {
Local<Value> value;
if (!values->Get(context, i).ToLocal(&value)) return false;
i::wasm::ValueType type = signature->get(i);
switch (type.kind()) {
case i::wasm::kI32: {
int32_t i32 = 0;
if (!ToI32(value, context, &i32)) return false;
i::EncodeI32ExceptionValue(values_out, &index, i32);
break;
}
case i::wasm::kI64: {
int64_t i64 = 0;
if (!ToI64(value, context, &i64)) return false;
i::EncodeI64ExceptionValue(values_out, &index, i64);
break;
}
case i::wasm::kF32: {
float f32 = 0;
if (!ToF32(value, context, &f32)) return false;
int32_t i32 = base::bit_cast<int32_t>(f32);
i::EncodeI32ExceptionValue(values_out, &index, i32);
break;
}
case i::wasm::kF64: {
double f64 = 0;
if (!ToF64(value, context, &f64)) return false;
int64_t i64 = base::bit_cast<int64_t>(f64);
i::EncodeI64ExceptionValue(values_out, &index, i64);
break;
}
case i::wasm::kRef:
case i::wasm::kRefNull: {
const char* error_message;
i::DirectHandle<i::Object> value_handle =
Utils::OpenDirectHandle(*value);
i::wasm::CanonicalValueType canonical_type = i::wasm::kWasmBottom;
if (type.has_index()) {
// Canonicalize the type using the tag's original module.
// Indexed types are guaranteed to come from an instance.
DCHECK(tag_object->has_trusted_data());
i::Tagged<i::WasmTrustedInstanceData> wtid =
tag_object->trusted_data(i_isolate);
const i::wasm::WasmModule* module = wtid->module();
canonical_type =
type.Canonicalize(module->canonical_type_id(type.ref_index()));
} else {
canonical_type = i::wasm::CanonicalValueType{type};
}
if (!i::wasm::JSToWasmObject(i_isolate, value_handle, canonical_type,
&error_message)
.ToHandle(&value_handle)) {
thrower->TypeError("%s", error_message);
return false;
}
values_out->set(index++, *value_handle);
break;
}
case i::wasm::kS128:
thrower->TypeError("Invalid type v128");
return false;
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kVoid:
case i::wasm::kTop:
case i::wasm::kBottom:
UNREACHABLE();
}
}
return true;
}
} // namespace
void WebAssemblyExceptionImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Exception()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Exception must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a WebAssembly tag");
return;
}
i::DirectHandle<i::Object> arg0 = Utils::OpenDirectHandle(*info[0]);
if (!IsWasmTagObject(i::Cast<i::HeapObject>(*arg0))) {
thrower.TypeError("Argument 0 must be a WebAssembly tag");
return;
}
auto tag_object = i::Cast<i::WasmTagObject>(arg0);
i::DirectHandle<i::WasmExceptionTag> tag(
i::Cast<i::WasmExceptionTag>(tag_object->tag()), i_isolate);
auto js_tag = i::Cast<i::WasmTagObject>(i_isolate->context()->wasm_js_tag());
if (*tag == js_tag->tag()) {
thrower.TypeError("Argument 0 cannot be WebAssembly.JSTag");
return;
}
const i::wasm::CanonicalSig* sig =
i::wasm::GetTypeCanonicalizer()->LookupFunctionSignature(
i::wasm::CanonicalTypeIndex{
static_cast<uint32_t>(tag_object->canonical_type_index())});
if (sig->return_count() != 0) {
thrower.TypeError(
"Invalid WebAssembly tag (return values not permitted in Exception "
"tag)");
return;
}
uint32_t size = GetEncodedSize(tag_object);
i::DirectHandle<i::WasmExceptionPackage> runtime_exception =
i::WasmExceptionPackage::New(i_isolate, tag, size);
// The constructor above should guarantee that the cast below succeeds.
i::DirectHandle<i::FixedArray> values =
i::Cast<i::FixedArray>(i::WasmExceptionPackage::GetExceptionValues(
i_isolate, runtime_exception));
i::DirectHandle<i::PodArray<i::wasm::ValueType>> signature(
tag_object->serialized_signature(), i_isolate);
if (!EncodeExceptionValues(isolate, signature, tag_object, info[1], &thrower,
values)) {
return js_api_scope.AssertException();
}
// Third argument: optional ExceptionOption ({traceStack: <bool>}).
if (!info[2]->IsNullOrUndefined() && !info[2]->IsObject()) {
thrower.TypeError("Argument 2 is not an object");
return;
}
if (info[2]->IsObject()) {
Local<Context> context = isolate->GetCurrentContext();
Local<Object> trace_stack_obj = Local<Object>::Cast(info[2]);
Local<String> trace_stack_key = v8_str(isolate, "traceStack");
v8::Local<Value> trace_stack_value;
if (!trace_stack_obj->Get(context, trace_stack_key)
.ToLocal(&trace_stack_value)) {
return js_api_scope.AssertException();
}
if (trace_stack_value->BooleanValue(isolate)) {
i::Handle<i::Object> caller = Utils::OpenHandle(*info.NewTarget());
i::DirectHandle<i::Object> capture_result;
if (!i::ErrorUtils::CaptureStackTrace(i_isolate, runtime_exception,
i::SKIP_NONE, caller)
.ToHandle(&capture_result)) {
return js_api_scope.AssertException();
}
}
}
info.GetReturnValue().Set(
Utils::ToLocal(i::Cast<i::Object>(runtime_exception)));
}
i::DirectHandle<i::JSFunction> NewPromisingWasmExportedFunction(
i::Isolate* i_isolate, i::DirectHandle<i::WasmExportedFunctionData> data,
ErrorThrower& thrower) {
i::DirectHandle<i::WasmTrustedInstanceData> trusted_instance_data{
data->instance_data(), i_isolate};
int func_index = data->function_index();
const i::wasm::WasmModule* module = trusted_instance_data->module();
i::wasm::ModuleTypeIndex sig_index = module->functions[func_index].sig_index;
const i::wasm::CanonicalSig* sig =
i::wasm::GetTypeCanonicalizer()->LookupFunctionSignature(
module->canonical_sig_id(sig_index));
i::DirectHandle<i::Code> wrapper;
if (!internal::wasm::IsJSCompatibleSignature(sig)) {
// If the signature is incompatible with JS, the original export will have
// compiled an incompatible signature wrapper, so just reuse that.
wrapper =
i::DirectHandle<i::Code>(data->wrapper_code(i_isolate), i_isolate);
} else {
wrapper = BUILTIN_CODE(i_isolate, WasmPromising);
}
// TODO(14034): Create funcref RTTs lazily?
i::DirectHandle<i::Map> rtt{
i::Cast<i::Map>(
trusted_instance_data->managed_object_maps()->get(sig_index.index)),
i_isolate};
int num_imported_functions = module->num_imported_functions;
i::DirectHandle<i::TrustedObject> implicit_arg;
if (func_index >= num_imported_functions) {
implicit_arg = trusted_instance_data;
} else {
implicit_arg = i_isolate->factory()->NewWasmImportData(direct_handle(
i::Cast<i::WasmImportData>(
trusted_instance_data->dispatch_table_for_imports()->implicit_arg(
func_index)),
i_isolate));
}
i::DirectHandle<i::WasmInternalFunction> internal =
i_isolate->factory()->NewWasmInternalFunction(implicit_arg, func_index);
i::DirectHandle<i::WasmFuncRef> func_ref =
i_isolate->factory()->NewWasmFuncRef(internal, rtt);
internal->set_call_target(trusted_instance_data->GetCallTarget(func_index));
if (func_index < num_imported_functions) {
i::Cast<i::WasmImportData>(implicit_arg)->set_call_origin(*internal);
}
i::DirectHandle<i::JSFunction> result = i::WasmExportedFunction::New(
i_isolate, trusted_instance_data, func_ref, internal,
static_cast<int>(data->sig()->parameter_count()), wrapper);
return result;
}
// WebAssembly.Function
void WebAssemblyFunction(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Function()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Function must be invoked with 'new'");
return;
}
if (!info[0]->IsObject()) {
thrower.TypeError("Argument 0 must be a function type");
return;
}
Local<Object> function_type = Local<Object>::Cast(info[0]);
Local<Context> context = isolate->GetCurrentContext();
auto enabled_features = WasmEnabledFeatures::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) ||
!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.
v8::Local<v8::Value> results_value;
if (!function_type->Get(context, v8_str(isolate, "results"))
.ToLocal(&results_value)) {
return js_api_scope.AssertException();
}
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 > 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) {
MaybeLocal<Value> maybe = parameters->Get(context, i);
std::optional<i::wasm::ValueType> maybe_type =
GetValueType(isolate, maybe, context, enabled_features);
if (!maybe_type) return;
i::wasm::ValueType type = *maybe_type;
if (type == i::wasm::kWasmVoid) {
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) {
MaybeLocal<Value> maybe = results->Get(context, i);
std::optional<i::wasm::ValueType> maybe_type =
GetValueType(isolate, maybe, context, enabled_features);
if (!maybe_type) return js_api_scope.AssertException();
i::wasm::ValueType type = *maybe_type;
if (type == i::wasm::kWasmVoid) {
thrower.TypeError(
"Argument 0 result type at index #%u must be a value type", i);
return;
}
builder.AddReturn(type);
}
if (!info[1]->IsObject()) {
thrower.TypeError("Argument 1 must be a function");
return;
}
const i::wasm::FunctionSig* sig = builder.Get();
i::wasm::Suspend suspend = i::wasm::kNoSuspend;
i::DirectHandle<i::JSReceiver> callable =
Utils::OpenDirectHandle(*info[1].As<Object>());
if (i::IsWasmSuspendingObject(*callable)) {
suspend = i::wasm::kSuspend;
callable = direct_handle(
i::Cast<i::WasmSuspendingObject>(*callable)->callable(), i_isolate);
DCHECK(i::IsCallable(*callable));
} else if (!i::IsCallable(*callable)) {
thrower.TypeError("Argument 1 must be a function");
return;
}
i::DirectHandle<i::JSFunction> result =
i::WasmJSFunction::New(i_isolate, sig, callable, suspend);
info.GetReturnValue().Set(Utils::ToLocal(result));
}
// WebAssembly.promising(Function) -> Function
void WebAssemblyPromising(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.promising()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i_isolate->CountUsage(v8::Isolate::kWasmJavaScriptPromiseIntegration);
if (!info[0]->IsFunction()) {
thrower.TypeError("Argument 0 must be a function");
return;
}
i::DirectHandle<i::JSReceiver> callable =
Utils::OpenDirectHandle(*info[0].As<Function>());
if (!i::WasmExportedFunction::IsWasmExportedFunction(*callable)) {
thrower.TypeError("Argument 0 must be a WebAssembly exported function");
return;
}
auto wasm_exported_function = i::Cast<i::WasmExportedFunction>(*callable);
i::DirectHandle<i::WasmExportedFunctionData> data(
wasm_exported_function->shared()->wasm_exported_function_data(),
i_isolate);
if (data->instance_data()->module_object()->is_asm_js()) {
thrower.TypeError("Argument 0 must be a WebAssembly exported function");
return;
}
i::DirectHandle<i::JSFunction> result =
NewPromisingWasmExportedFunction(i_isolate, data, thrower);
info.GetReturnValue().Set(Utils::ToLocal(i::Cast<i::JSObject>(result)));
}
// WebAssembly.Suspending(Function) -> Suspending
void WebAssemblySuspendingImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Suspending()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i_isolate->CountUsage(v8::Isolate::kWasmJavaScriptPromiseIntegration);
if (!info.IsConstructCall()) {
thrower.TypeError("WebAssembly.Suspending must be invoked with 'new'");
return;
}
if (!info[0]->IsFunction()) {
thrower.TypeError("Argument 0 must be a function");
return;
}
i::DirectHandle<i::JSReceiver> callable =
Utils::OpenDirectHandle(*info[0].As<Function>());
if (i::WasmExportedFunction::IsWasmExportedFunction(*callable) ||
i::WasmJSFunction::IsWasmJSFunction(*callable)) {
thrower.TypeError("Argument 0 must not be a WebAssembly function");
return;
}
i::DirectHandle<i::WasmSuspendingObject> result =
i::WasmSuspendingObject::New(i_isolate, callable);
info.GetReturnValue().Set(Utils::ToLocal(i::Cast<i::JSObject>(result)));
}
// WebAssembly.Function.prototype.type() -> FunctionType
void WebAssemblyFunctionType(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Function.type()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
i::DirectHandle<i::JSObject> type;
i::DirectHandle<i::Object> fun = Utils::OpenDirectHandle(*info.This());
if (i::WasmExportedFunction::IsWasmExportedFunction(*fun)) {
auto wasm_exported_function = i::Cast<i::WasmExportedFunction>(fun);
i::Tagged<i::WasmExportedFunctionData> data =
wasm_exported_function->shared()->wasm_exported_function_data();
// Note: while {zone} is only referenced directly in the if-block below,
// its lifetime must exceed that of {sig}.
// TODO(42210967): Creating a Zone just to create a modified copy of a
// single signature is rather expensive. It would be good to find a more
// efficient approach, if this function is ever considered performance
// relevant.
i::Zone zone(i_isolate->allocator(), ZONE_NAME);
const i::wasm::FunctionSig* sig =
data->instance_data()->module()->functions[data->function_index()].sig;
i::wasm::Promise promise_flags =
i::WasmFunctionData::PromiseField::decode(data->js_promise_flags());
if (promise_flags == i::wasm::kPromise) {
// The wrapper function returns a promise as an externref instead of the
// original return type.
size_t param_count = sig->parameter_count();
i::wasm::FunctionSig::Builder builder(&zone, 1, param_count);
for (size_t i = 0; i < param_count; ++i) {
builder.AddParam(sig->GetParam(i));
}
builder.AddReturn(i::wasm::kWasmExternRef);
sig = builder.Get();
}
type = i::wasm::GetTypeForFunction(i_isolate, sig);
} else if (i::WasmJSFunction::IsWasmJSFunction(*fun)) {
const i::wasm::CanonicalSig* sig = i::Cast<i::WasmJSFunction>(fun)
->shared()
->wasm_js_function_data()
->GetSignature();
// As long as WasmJSFunctions cannot use indexed types, their canonical
// signatures are bit-compatible with module-specific signatures.
#if DEBUG
for (i::wasm::CanonicalValueType t : sig->all()) {
DCHECK(!t.has_index());
}
#endif
static_assert(sizeof(i::wasm::ValueType) ==
sizeof(i::wasm::CanonicalValueType));
type = i::wasm::GetTypeForFunction(
i_isolate, reinterpret_cast<const i::wasm::FunctionSig*>(sig));
} else {
thrower.TypeError("Receiver must be a WebAssembly.Function");
return;
}
info.GetReturnValue().Set(Utils::ToLocal(type));
}
constexpr const char* kName_WasmGlobalObject = "WebAssembly.Global";
constexpr const char* kName_WasmMemoryObject = "WebAssembly.Memory";
constexpr const char* kName_WasmMemoryMapDescriptor =
"WebAssembly.MemoryMapDescriptor";
constexpr const char* kName_WasmInstanceObject = "WebAssembly.Instance";
constexpr const char* kName_WasmTableObject = "WebAssembly.Table";
constexpr const char* kName_WasmTagObject = "WebAssembly.Tag";
constexpr const char* kName_WasmExceptionPackage = "WebAssembly.Exception";
#define EXTRACT_THIS(var, WasmType) \
i::DirectHandle<i::WasmType> var; \
{ \
i::DirectHandle<i::Object> this_arg = \
Utils::OpenDirectHandle(*info.This()); \
if (!Is##WasmType(*this_arg)) { \
thrower.TypeError("Receiver is not a %s", kName_##WasmType); \
return; \
} \
var = i::Cast<i::WasmType>(this_arg); \
}
void WebAssemblyInstanceGetExportsImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Instance.exports()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmInstanceObject);
i::DirectHandle<i::JSObject> exports_object(receiver->exports_object(),
i_isolate);
info.GetReturnValue().Set(Utils::ToLocal(exports_object));
}
void WebAssemblyTableGetLengthImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table.length()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmTableObject);
int length = receiver->current_length();
DCHECK_LE(0, length);
info.GetReturnValue().Set(
AddressValueFromUnsigned(isolate, receiver->address_type(), length));
}
// WebAssembly.Table.grow(num, init_value = null) -> num
void WebAssemblyTableGrowImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table.grow()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
std::optional<uint64_t> maybe_grow_by = AddressValueToU64(
&thrower, context, info[0], "Argument 0", receiver->address_type());
if (!maybe_grow_by) return js_api_scope.AssertException();
uint64_t grow_by = *maybe_grow_by;
i::DirectHandle<i::Object> init_value;
if (info.Length() >= 2) {
init_value = Utils::OpenDirectHandle(*info[1]);
const char* error_message;
if (!i::WasmTableObject::JSToWasmElement(i_isolate, receiver, init_value,
&error_message)
.ToHandle(&init_value)) {
thrower.TypeError("Argument 1 is invalid: %s", error_message);
return;
}
} else if (receiver->unsafe_type().is_non_nullable()) {
thrower.TypeError(
"Argument 1 must be specified for non-nullable element type");
return;
} else {
init_value = DefaultReferenceValue(i_isolate, receiver->unsafe_type());
}
static_assert(i::wasm::kV8MaxWasmTableSize <= i::kMaxUInt32);
int old_size = grow_by > i::wasm::max_table_size()
? -1
: i::WasmTableObject::Grow(i_isolate, receiver,
static_cast<uint32_t>(grow_by),
init_value);
if (old_size < 0) {
thrower.RangeError("failed to grow table by %" PRIu64, grow_by);
return;
}
info.GetReturnValue().Set(
AddressValueFromUnsigned(isolate, receiver->address_type(), old_size));
}
namespace {
V8_WARN_UNUSED_RESULT bool WasmObjectToJSReturnValue(
v8::ReturnValue<v8::Value>& return_value, i::DirectHandle<i::Object> value,
i::wasm::ValueType type, i::Isolate* isolate, ErrorThrower* thrower) {
switch (type.heap_type().representation()) {
case internal::wasm::HeapType::kStringViewWtf8:
thrower->TypeError("%s", "stringview_wtf8 has no JS representation");
return false;
case internal::wasm::HeapType::kStringViewWtf16:
thrower->TypeError("%s", "stringview_wtf16 has no JS representation");
return false;
case internal::wasm::HeapType::kStringViewIter:
thrower->TypeError("%s", "stringview_iter has no JS representation");
return false;
case internal::wasm::HeapType::kExn:
case internal::wasm::HeapType::kNoExn:
case internal::wasm::HeapType::kCont:
case internal::wasm::HeapType::kNoCont:
thrower->TypeError("invalid type %s", type.name().c_str());
return false;
default:
return_value.Set(Utils::ToLocal(i::wasm::WasmToJSObject(isolate, value)));
return true;
}
}
} // namespace
// WebAssembly.Table.get(num) -> any
void WebAssemblyTableGetImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table.get()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmTableObject);
std::optional<uint64_t> maybe_address = AddressValueToU64(
&thrower, context, info[0], "Argument 0", receiver->address_type());
if (!maybe_address) return;
uint64_t address = *maybe_address;
if (address > i::kMaxUInt32 ||
!receiver->is_in_bounds(static_cast<uint32_t>(address))) {
thrower.RangeError("invalid address %" PRIu64 " in %s table of size %d",
address, receiver->unsafe_type().name().c_str(),
receiver->current_length());
return;
}
i::DirectHandle<i::Object> result = i::WasmTableObject::Get(
i_isolate, receiver, static_cast<uint32_t>(address));
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
if (!WasmObjectToJSReturnValue(return_value, result, receiver->unsafe_type(),
i_isolate, &thrower)) {
return js_api_scope.AssertException();
}
}
// WebAssembly.Table.set(num, any)
void WebAssemblyTableSetImpl(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table.set()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(table_object, WasmTableObject);
std::optional<uint64_t> maybe_address = AddressValueToU64(
&thrower, context, info[0], "Argument 0", table_object->address_type());
if (!maybe_address) return js_api_scope.AssertException();
uint64_t address = *maybe_address;
if (address > i::kMaxUInt32 ||
!table_object->is_in_bounds(static_cast<uint32_t>(address))) {
thrower.RangeError("invalid address %" PRIu64 " in %s table of size %d",
address, table_object->unsafe_type().name().c_str(),
table_object->current_length());
return;
}
i::DirectHandle<i::Object> element;
if (info.Length() >= 2) {
element = Utils::OpenDirectHandle(*info[1]);
const char* error_message;
if (!i::WasmTableObject::JSToWasmElement(i_isolate, table_object, element,
&error_message)
.ToHandle(&element)) {
thrower.TypeError("Argument 1 is invalid for table: %s", error_message);
return;
}
} else if (table_object->unsafe_type().is_defaultable()) {
element = DefaultReferenceValue(i_isolate, table_object->unsafe_type());
} else {
thrower.TypeError("Table of non-defaultable type %s needs explicit element",
table_object->unsafe_type().name().c_str());
return;
}
i::WasmTableObject::Set(i_isolate, table_object,
static_cast<uint32_t>(address), element);
}
// WebAssembly.Table.type() -> TableType
void WebAssemblyTableType(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Table.type()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(table, WasmTableObject);
std::optional<uint64_t> max_size = table->maximum_length_u64();
auto type = i::wasm::GetTypeForTable(i_isolate, table->unsafe_type(),
table->current_length(), max_size,
table->address_type());
info.GetReturnValue().Set(Utils::ToLocal(type));
}
// WebAssembly.MemoryMapDescriptor.map()
void WebAssemblyMemoryMapDescriptorMapImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::v8_flags.experimental_wasm_memory_control);
WasmJSApiScope js_api_scope{info, "WebAssembly.MemoryMapDescriptor.map()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmMemoryMapDescriptor);
i::DirectHandle<i::WasmMemoryObject> memory;
{
i::DirectHandle<i::Object> memory_param = Utils::OpenDirectHandle(*info[0]);
if (!i::IsWasmMemoryObject(*memory_param)) {
thrower.TypeError("Parameter is not a WebAssembly.Memory");
return js_api_scope.AssertException();
}
memory = i::Cast<i::WasmMemoryObject>(memory_param);
}
Local<Context> context = isolate->GetCurrentContext();
std::optional<uint32_t> offset =
EnforceUint32("Argument 1", info[1], context, &thrower);
if (!offset.has_value()) {
return js_api_scope.AssertException();
}
size_t mapped_size = receiver->MapDescriptor(memory, offset.value());
if (!mapped_size) {
thrower.RuntimeError(
"Failed to map the MemoryMapDescriptor to WebAssembly memory.");
return js_api_scope.AssertException();
}
receiver->set_memory(MakeWeak(*memory));
receiver->set_offset(offset.value());
receiver->set_size(static_cast<uint32_t>(mapped_size));
info.GetReturnValue().Set(static_cast<int64_t>(mapped_size));
}
// WebAssembly.MemoryMapDescriptor.unmap()
void WebAssemblyMemoryMapDescriptorUnmapImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::v8_flags.experimental_wasm_memory_control);
WasmJSApiScope js_api_scope{info, "WebAssembly.MemoryMapDescriptor.unmap()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmMemoryMapDescriptor);
if (!receiver->UnmapDescriptor()) {
thrower.RangeError("Failed to unmap the MemoryMapDescriptor.");
return;
}
}
// WebAssembly.Memory.grow(num) -> num
void WebAssemblyMemoryGrowImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Memory.grow()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
Local<Context> context = isolate->GetCurrentContext();
EXTRACT_THIS(receiver, WasmMemoryObject);
std::optional<uint64_t> maybe_delta_pages = AddressValueToU64(
&thrower, context, info[0], "Argument 0", receiver->address_type());
if (!maybe_delta_pages) return js_api_scope.AssertException();
uint64_t delta_pages = *maybe_delta_pages;
i::DirectHandle<i::JSArrayBuffer> old_buffer(receiver->array_buffer(),
i_isolate);
uint64_t old_pages = old_buffer->byte_length() / i::wasm::kWasmPageSize;
uint64_t max_pages = receiver->maximum_pages();
if (delta_pages > max_pages - old_pages) {
thrower.RangeError("Maximum memory size exceeded");
return;
}
static_assert(i::wasm::kV8MaxWasmMemory64Pages <= i::kMaxUInt32);
int32_t ret = i::WasmMemoryObject::Grow(i_isolate, receiver,
static_cast<uint32_t>(delta_pages));
if (ret == -1) {
thrower.RangeError("Unable to grow instance memory");
return;
}
info.GetReturnValue().Set(
AddressValueFromUnsigned(isolate, receiver->address_type(), ret));
}
// WebAssembly.Memory.buffer -> ArrayBuffer
void WebAssemblyMemoryGetBufferImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Memory.buffer"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmMemoryObject);
i::DirectHandle<i::Object> buffer_obj(receiver->array_buffer(), i_isolate);
DCHECK(IsJSArrayBuffer(*buffer_obj));
i::DirectHandle<i::JSArrayBuffer> buffer(
i::Cast<i::JSArrayBuffer>(*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(i_isolate, buffer, i::FROZEN, i::kDontThrow);
if (!result.FromJust()) {
thrower.TypeError(
"Status of setting SetIntegrityLevel of buffer is false.");
return;
}
}
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
return_value.Set(Utils::ToLocal(buffer));
}
// WebAssembly.Memory.type() -> MemoryType
void WebAssemblyMemoryType(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Memory.type()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(memory, WasmMemoryObject);
i::DirectHandle<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);
std::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));
}
bool shared = buffer->is_shared();
auto type = i::wasm::GetTypeForMemory(i_isolate, min_size, max_size, shared,
memory->address_type());
info.GetReturnValue().Set(Utils::ToLocal(type));
}
// WebAssembly.Tag.type() -> FunctionType
void WebAssemblyTagType(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Tag.type()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(tag, WasmTagObject);
int n = tag->serialized_signature()->length();
std::vector<i::wasm::ValueType> data(n);
if (n > 0) {
tag->serialized_signature()->copy_out(0, data.data(), n);
}
const i::wasm::FunctionSig sig{0, data.size(), data.data()};
constexpr bool kForException = true;
auto type = i::wasm::GetTypeForFunction(i_isolate, &sig, kForException);
info.GetReturnValue().Set(Utils::ToLocal(type));
}
void WebAssemblyExceptionGetArgImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Exception.getArg()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(exception, WasmExceptionPackage);
i::DirectHandle<i::WasmTagObject> tag_object;
if (!GetFirstArgumentAsTag(info, &thrower).ToHandle(&tag_object)) {
return js_api_scope.AssertException();
}
Local<Context> context = isolate->GetCurrentContext();
std::optional<uint32_t> maybe_index =
EnforceUint32("Index", info[1], context, &thrower);
if (!maybe_index) return js_api_scope.AssertException();
uint32_t index = *maybe_index;
auto maybe_values =
i::WasmExceptionPackage::GetExceptionValues(i_isolate, exception);
auto this_tag =
i::WasmExceptionPackage::GetExceptionTag(i_isolate, exception);
DCHECK(IsWasmExceptionTag(*this_tag));
if (tag_object->tag() != *this_tag) {
thrower.TypeError("First argument does not match the exception tag");
return;
}
DCHECK(!IsUndefined(*maybe_values));
auto values = i::Cast<i::FixedArray>(maybe_values);
auto signature = tag_object->serialized_signature();
if (index >= static_cast<uint32_t>(signature->length())) {
thrower.RangeError("Index out of range");
return;
}
// First, find the index in the values array.
uint32_t decode_index = 0;
// Since the bounds check above passed, the cast to int is safe.
for (int i = 0; i < static_cast<int>(index); ++i) {
switch (signature->get(i).kind()) {
case i::wasm::kI32:
case i::wasm::kF32:
decode_index += 2;
break;
case i::wasm::kI64:
case i::wasm::kF64:
decode_index += 4;
break;
case i::wasm::kRef:
case i::wasm::kRefNull:
decode_index++;
break;
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kVoid:
case i::wasm::kTop:
case i::wasm::kBottom:
case i::wasm::kS128:
UNREACHABLE();
}
}
// Decode the value at {decode_index}.
Local<Value> result;
switch (signature->get(index).kind()) {
case i::wasm::kI32: {
uint32_t u32_bits = 0;
i::DecodeI32ExceptionValue(values, &decode_index, &u32_bits);
int32_t i32 = static_cast<int32_t>(u32_bits);
result = v8::Integer::New(isolate, i32);
break;
}
case i::wasm::kI64: {
uint64_t u64_bits = 0;
i::DecodeI64ExceptionValue(values, &decode_index, &u64_bits);
int64_t i64 = static_cast<int64_t>(u64_bits);
result = v8::BigInt::New(isolate, i64);
break;
}
case i::wasm::kF32: {
uint32_t f32_bits = 0;
DecodeI32ExceptionValue(values, &decode_index, &f32_bits);
float f32 = base::bit_cast<float>(f32_bits);
result = v8::Number::New(isolate, f32);
break;
}
case i::wasm::kF64: {
uint64_t f64_bits = 0;
DecodeI64ExceptionValue(values, &decode_index, &f64_bits);
double f64 = base::bit_cast<double>(f64_bits);
result = v8::Number::New(isolate, f64);
break;
}
case i::wasm::kRef:
case i::wasm::kRefNull: {
i::DirectHandle<i::Object> obj(values->get(decode_index), i_isolate);
ReturnValue<Value> return_value = info.GetReturnValue();
if (!WasmObjectToJSReturnValue(return_value, obj, signature->get(index),
i_isolate, &thrower)) {
return js_api_scope.AssertException();
}
return;
}
case i::wasm::kS128:
thrower.TypeError("Invalid type v128");
return;
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kVoid:
case i::wasm::kTop:
case i::wasm::kBottom:
UNREACHABLE();
}
info.GetReturnValue().Set(result);
}
void WebAssemblyExceptionIsImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Exception.is()"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(exception, WasmExceptionPackage);
auto tag = i::WasmExceptionPackage::GetExceptionTag(i_isolate, exception);
DCHECK(IsWasmExceptionTag(*tag));
i::DirectHandle<i::WasmTagObject> tag_object;
if (!GetFirstArgumentAsTag(info, &thrower).ToHandle(&tag_object)) {
return js_api_scope.AssertException();
}
info.GetReturnValue().Set(tag_object->tag() == *tag);
}
void WebAssemblyGlobalGetValueCommon(WasmJSApiScope& js_api_scope) {
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
auto& info = js_api_scope.callback_info(); // Needed by EXTRACT_THIS.
EXTRACT_THIS(receiver, WasmGlobalObject);
v8::ReturnValue<v8::Value> return_value = info.GetReturnValue();
switch (receiver->type().kind()) {
case i::wasm::kI32:
return_value.Set(receiver->GetI32());
break;
case i::wasm::kI64: {
Local<BigInt> value = BigInt::New(isolate, receiver->GetI64());
return_value.Set(value);
break;
}
case i::wasm::kF32:
return_value.Set(receiver->GetF32());
break;
case i::wasm::kF64:
return_value.Set(receiver->GetF64());
break;
case i::wasm::kS128:
thrower.TypeError("Can't get the value of s128 WebAssembly.Global");
break;
case i::wasm::kRef:
case i::wasm::kRefNull:
if (!WasmObjectToJSReturnValue(return_value, receiver->GetRef(),
receiver->type(), i_isolate, &thrower)) {
return js_api_scope.AssertException();
}
break;
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kTop:
case i::wasm::kBottom:
case i::wasm::kVoid:
UNREACHABLE();
}
}
// WebAssembly.Global.valueOf() -> num
void WebAssemblyGlobalValueOfImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Global.valueOf()"};
return WebAssemblyGlobalGetValueCommon(js_api_scope);
}
// get WebAssembly.Global.value -> num
void WebAssemblyGlobalGetValueImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "get WebAssembly.Global.value)"};
return WebAssemblyGlobalGetValueCommon(js_api_scope);
}
// set WebAssembly.Global.value(num)
void WebAssemblyGlobalSetValueImpl(
const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "set WebAssembly.Global.value)"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(receiver, WasmGlobalObject);
if (!receiver->is_mutable()) {
thrower.TypeError("Can't set the value of an immutable global.");
return;
}
if (info.Length() == 0) {
thrower.TypeError("Argument 0 is required");
return;
}
Local<Context> context = isolate->GetCurrentContext();
switch (receiver->type().kind()) {
case i::wasm::kI32: {
int32_t i32_value = 0;
if (!info[0]->Int32Value(context).To(&i32_value)) {
return js_api_scope.AssertException();
}
receiver->SetI32(i32_value);
break;
}
case i::wasm::kI64: {
v8::Local<v8::BigInt> bigint_value;
if (!info[0]->ToBigInt(context).ToLocal(&bigint_value)) {
return js_api_scope.AssertException();
}
receiver->SetI64(bigint_value->Int64Value());
break;
}
case i::wasm::kF32: {
double f64_value = 0;
if (!info[0]->NumberValue(context).To(&f64_value)) {
return js_api_scope.AssertException();
}
receiver->SetF32(i::DoubleToFloat32(f64_value));
break;
}
case i::wasm::kF64: {
double f64_value = 0;
if (!info[0]->NumberValue(context).To(&f64_value)) {
return js_api_scope.AssertException();
}
receiver->SetF64(f64_value);
break;
}
case i::wasm::kS128:
thrower.TypeError("Can't set the value of s128 WebAssembly.Global");
break;
case i::wasm::kRef:
case i::wasm::kRefNull: {
const i::wasm::WasmModule* module =
receiver->has_trusted_data()
? receiver->trusted_data(i_isolate)->module()
: nullptr;
i::DirectHandle<i::Object> value = Utils::OpenDirectHandle(*info[0]);
const char* error_message;
if (!i::wasm::JSToWasmObject(i_isolate, module, value, receiver->type(),
&error_message)
.ToHandle(&value)) {
thrower.TypeError("%s", error_message);
return;
}
receiver->SetRef(value);
return;
}
case i::wasm::kI8:
case i::wasm::kI16:
case i::wasm::kF16:
case i::wasm::kTop:
case i::wasm::kBottom:
case i::wasm::kVoid:
UNREACHABLE();
}
}
// WebAssembly.Global.type() -> GlobalType
void WebAssemblyGlobalType(const v8::FunctionCallbackInfo<v8::Value>& info) {
WasmJSApiScope js_api_scope{info, "WebAssembly.Global.type())"};
auto [isolate, i_isolate, thrower] = js_api_scope.isolates_and_thrower();
EXTRACT_THIS(global, WasmGlobalObject);
auto type = i::wasm::GetTypeForGlobal(i_isolate, global->is_mutable(),
global->type());
info.GetReturnValue().Set(Utils::ToLocal(type));
}
} // namespace
namespace internal {
namespace wasm {
// Define the callbacks in v8::internal::wasm namespace. The implementation is
// in v8::internal directly.
#define DEF_WASM_JS_EXTERNAL_REFERENCE(Name) \
void Name(const v8::FunctionCallbackInfo<v8::Value>& info) { \
Name##Impl(info); \
}
WASM_JS_EXTERNAL_REFERENCE_LIST(DEF_WASM_JS_EXTERNAL_REFERENCE)
#undef DEF_WASM_JS_EXTERNAL_REFERENCE
} // namespace wasm
} // namespace internal
// TODO(titzer): we use the API to create the function template because the
// internal guts are too ugly to replicate here.
static i::DirectHandle<i::FunctionTemplateInfo> NewFunctionTemplate(
i::Isolate* i_isolate, FunctionCallback func, bool has_prototype,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect) {
Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate);
ConstructorBehavior behavior =
has_prototype ? ConstructorBehavior::kAllow : ConstructorBehavior::kThrow;
Local<FunctionTemplate> templ = FunctionTemplate::New(
isolate, func, {}, {}, 0, behavior, side_effect_type);
if (has_prototype) templ->ReadOnlyPrototype();
return v8::Utils::OpenDirectHandle(*templ);
}
static i::DirectHandle<i::ObjectTemplateInfo> NewObjectTemplate(
i::Isolate* i_isolate) {
Isolate* isolate = reinterpret_cast<Isolate*>(i_isolate);
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
return v8::Utils::OpenDirectHandle(*templ);
}
namespace internal {
namespace {
DirectHandle<JSFunction> CreateFunc(
Isolate* isolate, DirectHandle<String> name, FunctionCallback func,
bool has_prototype,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect,
DirectHandle<FunctionTemplateInfo> parent = {}) {
DirectHandle<FunctionTemplateInfo> temp =
NewFunctionTemplate(isolate, func, has_prototype, side_effect_type);
if (!parent.is_null()) {
DCHECK(has_prototype);
FunctionTemplateInfo::SetParentTemplate(isolate, temp, parent);
}
DirectHandle<JSFunction> function =
ApiNatives::InstantiateFunction(isolate, temp, name).ToHandleChecked();
DCHECK(function->shared()->HasSharedName());
return function;
}
DirectHandle<JSFunction> InstallFunc(
Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<String> name,
FunctionCallback func, int length, bool has_prototype = false,
PropertyAttributes attributes = NONE,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect) {
DirectHandle<JSFunction> function =
CreateFunc(isolate, name, func, has_prototype, side_effect_type);
function->shared()->set_length(length);
CHECK(!JSObject::HasRealNamedProperty(isolate, object, name).FromMaybe(true));
CHECK(object->map()->is_extensible());
JSObject::AddProperty(isolate, object, name, function, attributes);
return function;
}
DirectHandle<JSFunction> InstallFunc(
Isolate* isolate, DirectHandle<JSObject> object, const char* str,
FunctionCallback func, int length, bool has_prototype = false,
PropertyAttributes attributes = NONE,
SideEffectType side_effect_type = SideEffectType::kHasSideEffect) {
DirectHandle<String> name = v8_str(isolate, str);
return InstallFunc(isolate, object, name, func, length, has_prototype,
attributes, side_effect_type);
}
DirectHandle<JSFunction> InstallConstructorFunc(Isolate* isolate,
DirectHandle<JSObject> object,
const char* str,
FunctionCallback func) {
return InstallFunc(isolate, object, str, func, 1, true, DONT_ENUM,
SideEffectType::kHasNoSideEffect);
}
DirectHandle<String> GetterName(Isolate* isolate, DirectHandle<String> name) {
return Name::ToFunctionName(isolate, name, isolate->factory()->get_string())
.ToHandleChecked();
}
void InstallGetter(Isolate* isolate, DirectHandle<JSObject> object,
const char* str, FunctionCallback func) {
DirectHandle<String> name = v8_str(isolate, str);
DirectHandle<JSFunction> function =
CreateFunc(isolate, GetterName(isolate, name), func, false,
SideEffectType::kHasNoSideEffect);
Utils::ToLocal(object)->SetAccessorProperty(Utils::ToLocal(name),
Utils::ToLocal(function),
Local<Function>(), v8::None);
}
DirectHandle<String> SetterName(Isolate* isolate, DirectHandle<String> name) {
return Name::ToFunctionName(isolate, name, isolate->factory()->set_string())
.ToHandleChecked();
}
void InstallGetterSetter(Isolate* isolate, DirectHandle<JSObject> object,
const char* str, FunctionCallback getter,
FunctionCallback setter) {
DirectHandle<String> name = v8_str(isolate, str);
DirectHandle<JSFunction> getter_func =
CreateFunc(isolate, GetterName(isolate, name), getter, false,
SideEffectType::kHasNoSideEffect);
DirectHandle<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, DirectHandle<JSFunction> fun) {
DirectHandle<ObjectTemplateInfo> instance_template =
NewObjectTemplate(isolate);
FunctionTemplateInfo::SetInstanceTemplate(
isolate, direct_handle(fun->shared()->api_func_data(), isolate),
instance_template);
}
DirectHandle<JSObject> SetupConstructor(Isolate* isolate,
DirectHandle<JSFunction> constructor,
InstanceType instance_type,
int instance_size,
const char* name = nullptr,
int in_object_properties = 0) {
SetDummyInstanceTemplate(isolate, constructor);
JSFunction::EnsureHasInitialMap(constructor);
DirectHandle<JSObject> proto(
Cast<JSObject>(constructor->instance_prototype()), isolate);
DirectHandle<Map> map = isolate->factory()->NewContextfulMap(
constructor, instance_type, instance_size, TERMINAL_FAST_ELEMENTS_KIND,
in_object_properties);
JSFunction::SetInitialMap(isolate, constructor, map, proto);
constexpr PropertyAttributes ro_attributes =
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
if (name) {
JSObject::AddProperty(isolate, proto,
isolate->factory()->to_string_tag_symbol(),
v8_str(isolate, name), ro_attributes);
}
return proto;
}
constexpr wasm::ValueType kWasmExceptionTagParams[] = {
wasm::kWasmExternRef,
};
constexpr wasm::FunctionSig kWasmExceptionTagSignature{
0, arraysize(kWasmExceptionTagParams), kWasmExceptionTagParams};
} // namespace
// static
void WasmJs::PrepareForSnapshot(Isolate* isolate) {
DirectHandle<JSGlobalObject> global = isolate->global_object();
DirectHandle<NativeContext> native_context(global->native_context(), isolate);
CHECK(IsUndefined(native_context->get(Context::WASM_WEBASSEMBLY_OBJECT_INDEX),
isolate));
CHECK(IsUndefined(native_context->get(Context::WASM_MODULE_CONSTRUCTOR_INDEX),
isolate));
Factory* const f = isolate->factory();
static constexpr PropertyAttributes ro_attributes =
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
// Create the WebAssembly object.
DirectHandle<JSObject> webassembly;
{
DirectHandle<String> WebAssembly_string = v8_str(isolate, "WebAssembly");
// Not supposed to be called, hence using the kIllegal builtin as code.
DirectHandle<SharedFunctionInfo> sfi = f->NewSharedFunctionInfoForBuiltin(
WebAssembly_string, Builtin::kIllegal, 0, kDontAdapt);
sfi->set_language_mode(LanguageMode::kStrict);
DirectHandle<JSFunction> ctor =
Factory::JSFunctionBuilder{isolate, sfi, native_context}.Build();
JSFunction::SetPrototype(ctor, isolate->initial_object_prototype());
webassembly = f->NewJSObject(ctor, AllocationType::kOld);
native_context->set_wasm_webassembly_object(*webassembly);
JSObject::AddProperty(isolate, webassembly, f->to_string_tag_symbol(),
WebAssembly_string, ro_attributes);
InstallFunc(isolate, webassembly, "compile", wasm::WebAssemblyCompile, 1);
InstallFunc(isolate, webassembly, "validate", wasm::WebAssemblyValidate, 1);
InstallFunc(isolate, webassembly, "instantiate",
wasm::WebAssemblyInstantiate, 1);
}
// Create the Module object.
InstallModule(isolate, webassembly);
// Create the Instance object.
{
DirectHandle<JSFunction> instance_constructor = InstallConstructorFunc(
isolate, webassembly, "Instance", wasm::WebAssemblyInstance);
DirectHandle<JSObject> instance_proto = SetupConstructor(
isolate, instance_constructor, WASM_INSTANCE_OBJECT_TYPE,
WasmInstanceObject::kHeaderSize, "WebAssembly.Instance");
native_context->set_wasm_instance_constructor(*instance_constructor);
InstallGetter(isolate, instance_proto, "exports",
wasm::WebAssemblyInstanceGetExports);
}
// Create the Table object.
{
DirectHandle<JSFunction> table_constructor = InstallConstructorFunc(
isolate, webassembly, "Table", wasm::WebAssemblyTable);
DirectHandle<JSObject> table_proto =
SetupConstructor(isolate, table_constructor, WASM_TABLE_OBJECT_TYPE,
WasmTableObject::kHeaderSize, "WebAssembly.Table");
native_context->set_wasm_table_constructor(*table_constructor);
InstallGetter(isolate, table_proto, "length",
wasm::WebAssemblyTableGetLength);
InstallFunc(isolate, table_proto, "grow", wasm::WebAssemblyTableGrow, 1);
InstallFunc(isolate, table_proto, "set", wasm::WebAssemblyTableSet, 1);
InstallFunc(isolate, table_proto, "get", wasm::WebAssemblyTableGet, 1,
false, NONE, SideEffectType::kHasNoSideEffect);
}
// Create the Memory object.
{
DirectHandle<JSFunction> memory_constructor = InstallConstructorFunc(
isolate, webassembly, "Memory", wasm::WebAssemblyMemory);
DirectHandle<JSObject> memory_proto =
SetupConstructor(isolate, memory_constructor, WASM_MEMORY_OBJECT_TYPE,
WasmMemoryObject::kHeaderSize, "WebAssembly.Memory");
native_context->set_wasm_memory_constructor(*memory_constructor);
InstallFunc(isolate, memory_proto, "grow", wasm::WebAssemblyMemoryGrow, 1);
InstallGetter(isolate, memory_proto, "buffer",
wasm::WebAssemblyMemoryGetBuffer);
}
// Create the Global object.
{
DirectHandle<JSFunction> global_constructor = InstallConstructorFunc(
isolate, webassembly, "Global", wasm::WebAssemblyGlobal);
DirectHandle<JSObject> global_proto =
SetupConstructor(isolate, global_constructor, WASM_GLOBAL_OBJECT_TYPE,
WasmGlobalObject::kHeaderSize, "WebAssembly.Global");
native_context->set_wasm_global_constructor(*global_constructor);
InstallFunc(isolate, global_proto, "valueOf",
wasm::WebAssemblyGlobalValueOf, 0, false, NONE,
SideEffectType::kHasNoSideEffect);
InstallGetterSetter(isolate, global_proto, "value",
wasm::WebAssemblyGlobalGetValue,
wasm::WebAssemblyGlobalSetValue);
}
// Create the Exception object.
{
DirectHandle<JSFunction> tag_constructor = InstallConstructorFunc(
isolate, webassembly, "Tag", wasm::WebAssemblyTag);
SetupConstructor(isolate, tag_constructor, WASM_TAG_OBJECT_TYPE,
WasmTagObject::kHeaderSize, "WebAssembly.Tag");
native_context->set_wasm_tag_constructor(*tag_constructor);
auto js_tag = WasmExceptionTag::New(isolate, 0);
// Note the canonical_type_index is reset in WasmJs::Install s.t.
// type_canonicalizer bookkeeping remains valid.
static constexpr wasm::CanonicalTypeIndex kInitialCanonicalTypeIndex{0};
DirectHandle<JSObject> js_tag_object = WasmTagObject::New(
isolate, &kWasmExceptionTagSignature, kInitialCanonicalTypeIndex,
js_tag, DirectHandle<WasmTrustedInstanceData>());
native_context->set_wasm_js_tag(*js_tag_object);
JSObject::AddProperty(isolate, webassembly, "JSTag", js_tag_object,
ro_attributes);
}
// Set up the runtime exception constructor.
{
DirectHandle<JSFunction> exception_constructor = InstallConstructorFunc(
isolate, webassembly, "Exception", wasm::WebAssemblyException);
SetDummyInstanceTemplate(isolate, exception_constructor);
DirectHandle<JSObject> exception_proto = SetupConstructor(
isolate, exception_constructor, WASM_EXCEPTION_PACKAGE_TYPE,
WasmExceptionPackage::kSize, "WebAssembly.Exception",
WasmExceptionPackage::kInObjectFieldCount);
InstallFunc(isolate, exception_proto, "getArg",
wasm::WebAssemblyExceptionGetArg, 2);
InstallFunc(isolate, exception_proto, "is", wasm::WebAssemblyExceptionIs,
1);
native_context->set_wasm_exception_constructor(*exception_constructor);
DirectHandle<Map> initial_map(exception_constructor->initial_map(),
isolate);
Map::EnsureDescriptorSlack(isolate, initial_map, 2);
{
Descriptor d = Descriptor::DataField(
isolate, f->wasm_exception_tag_symbol(),
WasmExceptionPackage::kTagIndex, DONT_ENUM, Representation::Tagged());
initial_map->AppendDescriptor(isolate, &d);
}
{
Descriptor d =
Descriptor::DataField(isolate, f->wasm_exception_values_symbol(),
WasmExceptionPackage::kValuesIndex, DONT_ENUM,
Representation::Tagged());
initial_map->AppendDescriptor(isolate, &d);
}
}
// By default, make all exported functions an instance of {Function}.
{
DirectHandle<Map> function_map =
isolate->sloppy_function_without_prototype_map();
native_context->set_wasm_exported_function_map(*function_map);
}
// Setup errors.
{
InstallError(isolate, webassembly, f->CompileError_string(),
Context::WASM_COMPILE_ERROR_FUNCTION_INDEX);
InstallError(isolate, webassembly, f->LinkError_string(),
Context::WASM_LINK_ERROR_FUNCTION_INDEX);
InstallError(isolate, webassembly, f->RuntimeError_string(),
Context::WASM_RUNTIME_ERROR_FUNCTION_INDEX);
}
}
void WasmJs::InstallModule(Isolate* isolate,
DirectHandle<JSObject> webassembly) {
DirectHandle<JSGlobalObject> global = isolate->global_object();
DirectHandle<NativeContext> native_context(global->native_context(), isolate);
DirectHandle<JSFunction> module_constructor;
if (v8_flags.js_source_phase_imports) {
DirectHandle<FunctionTemplateInfo>
intrinsic_abstract_module_source_interface_template =
NewFunctionTemplate(isolate, nullptr, false);
DirectHandle<JSObject> abstract_module_source_prototype =
DirectHandle<JSObject>(
native_context->abstract_module_source_prototype(), isolate);
ApiNatives::AddDataProperty(
isolate, intrinsic_abstract_module_source_interface_template,
v8_str(isolate, "prototype"), abstract_module_source_prototype, NONE);
// Check that this is a reinstallation of the Module object.
DirectHandle<String> name = v8_str(isolate, "Module");
CHECK(
JSObject::HasRealNamedProperty(isolate, webassembly, name).ToChecked());
// Reinstall the Module object with AbstractModuleSource as prototype.
module_constructor =
CreateFunc(isolate, name, wasm::WebAssemblyModule, true,
SideEffectType::kHasNoSideEffect,
intrinsic_abstract_module_source_interface_template);
module_constructor->shared()->set_length(1);
JSObject::SetOwnPropertyIgnoreAttributes(webassembly, name,
module_constructor, DONT_ENUM)
.Assert();
} else {
module_constructor = InstallConstructorFunc(isolate, webassembly, "Module",
wasm::WebAssemblyModule);
}
SetupConstructor(isolate, module_constructor, WASM_MODULE_OBJECT_TYPE,
WasmModuleObject::kHeaderSize, "WebAssembly.Module");
native_context->set_wasm_module_constructor(*module_constructor);
InstallFunc(isolate, module_constructor, "imports",
wasm::WebAssemblyModuleImports, 1, false, NONE,
SideEffectType::kHasNoSideEffect);
InstallFunc(isolate, module_constructor, "exports",
wasm::WebAssemblyModuleExports, 1, false, NONE,
SideEffectType::kHasNoSideEffect);
InstallFunc(isolate, module_constructor, "customSections",
wasm::WebAssemblyModuleCustomSections, 2, false, NONE,
SideEffectType::kHasNoSideEffect);
}
// static
void WasmJs::Install(Isolate* isolate) {
DirectHandle<JSGlobalObject> global = isolate->global_object();
DirectHandle<NativeContext> native_context(global->native_context(), isolate);
if (native_context->is_wasm_js_installed() != Smi::zero()) return;
native_context->set_is_wasm_js_installed(Smi::FromInt(1));
// We always use the WebAssembly object from the native context; as this code
// is executed before any user code, this is expected to be the same as the
// global "WebAssembly" property. But even later during execution we always
// want to use this preallocated object instead of whatever user code
// installed as "WebAssembly" property.
DirectHandle<JSObject> webassembly(native_context->wasm_webassembly_object(),
isolate);
if (v8_flags.js_source_phase_imports) {
// Reinstall the Module object with the experimental interface.
InstallModule(isolate, webassembly);
}
// Expose the API on the global object if not in jitless mode (with more
// subtleties).
//
// Even in interpreter-only mode, wasm currently still creates executable
// memory at runtime. Unexpose wasm until this changes.
// The correctness fuzzers are a special case: many of their test cases are
// built by fetching a random property from the the global object, and thus
// the global object layout must not change between configs. That is why we
// continue exposing wasm on correctness fuzzers even in jitless mode.
// TODO(jgruber): Remove this once / if wasm can run without executable
// memory.
bool expose_wasm = !i::v8_flags.jitless ||
i::v8_flags.correctness_fuzzer_suppressions ||
i::v8_flags.wasm_jitless;
if (expose_wasm) {
DirectHandle<String> WebAssembly_string = v8_str(isolate, "WebAssembly");
JSObject::AddProperty(isolate, global, WebAssembly_string, webassembly,
DONT_ENUM);
}
{
// Reset the JSTag's canonical_type_index based on this Isolate's
// type_canonicalizer.
DirectHandle<WasmTagObject> js_tag_object(
Cast<WasmTagObject>(native_context->wasm_js_tag()), isolate);
js_tag_object->set_canonical_type_index(
wasm::GetWasmEngine()
->type_canonicalizer()
->AddRecursiveGroup(&kWasmExceptionTagSignature)
.index);
}
if (v8_flags.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);
}
// The native_context is not set up completely yet. That's why we cannot use
// {WasmEnabledFeatures::FromIsolate} and have to use
// {WasmEnabledFeatures::FromFlags} instead.
const auto enabled_features = wasm::WasmEnabledFeatures::FromFlags();
if (enabled_features.has_type_reflection()) {
InstallTypeReflection(isolate, native_context, webassembly);
}
if (enabled_features.has_memory_control()) {
InstallMemoryControl(isolate, native_context, webassembly);
}
// Initialize and install JSPI feature.
if (enabled_features.has_jspi()) {
CHECK(native_context->is_wasm_jspi_installed() == Smi::zero());
isolate->WasmInitJSPIFeature();
InstallJSPromiseIntegration(isolate, native_context, webassembly);
native_context->set_is_wasm_jspi_installed(Smi::FromInt(1));
} else if (v8_flags.stress_wasm_stack_switching) {
// Set up the JSPI objects necessary for stress-testing stack-switching, but
// don't install WebAssembly.promising and WebAssembly.Suspending.
isolate->WasmInitJSPIFeature();
}
}
// static
void WasmJs::InstallConditionalFeatures(Isolate* isolate,
DirectHandle<NativeContext> context) {
DirectHandle<JSObject> webassembly{context->wasm_webassembly_object(),
isolate};
if (!webassembly->map()->is_extensible()) return;
if (webassembly->map()->is_access_check_needed()) return;
// If you need to install some optional features, follow the pattern:
//
// if (isolate->IsMyWasmFeatureEnabled(context)) {
// DirectHandle<String> feature = isolate->factory()->...;
// if (!JSObject::HasRealNamedProperty(isolate, webassembly, feature)
// .FromMaybe(true)) {
// InstallFeature(isolate, webassembly);
// }
// }
// Install JSPI-related features.
if (isolate->IsWasmJSPIRequested(context)) {
if (context->is_wasm_jspi_installed() == Smi::zero()) {
isolate->WasmInitJSPIFeature();
if (InstallJSPromiseIntegration(isolate, context, webassembly) &&
InstallTypeReflection(isolate, context, webassembly)) {
context->set_is_wasm_jspi_installed(Smi::FromInt(1));
}
}
}
}
// static
// Return true if this call results in JSPI being installed.
bool WasmJs::InstallJSPromiseIntegration(Isolate* isolate,
DirectHandle<NativeContext> context,
DirectHandle<JSObject> webassembly) {
DirectHandle<String> suspender_string = v8_str(isolate, "Suspender");
if (JSObject::HasRealNamedProperty(isolate, webassembly, suspender_string)
.FromMaybe(true)) {
return false;
}
DirectHandle<String> suspending_string = v8_str(isolate, "Suspending");
if (JSObject::HasRealNamedProperty(isolate, webassembly, suspending_string)
.FromMaybe(true)) {
return false;
}
DirectHandle<String> promising_string = v8_str(isolate, "promising");
if (JSObject::HasRealNamedProperty(isolate, webassembly, promising_string)
.FromMaybe(true)) {
return false;
}
DirectHandle<String> suspend_error_string = v8_str(isolate, "SuspendError");
if (JSObject::HasRealNamedProperty(isolate, webassembly, suspend_error_string)
.FromMaybe(true)) {
return false;
}
DirectHandle<JSFunction> suspending_constructor = InstallConstructorFunc(
isolate, webassembly, "Suspending", WebAssemblySuspendingImpl);
context->set_wasm_suspending_constructor(*suspending_constructor);
SetupConstructor(isolate, suspending_constructor, WASM_SUSPENDING_OBJECT_TYPE,
WasmSuspendingObject::kHeaderSize, "WebAssembly.Suspending");
InstallFunc(isolate, webassembly, "promising", WebAssemblyPromising, 1);
InstallError(isolate, webassembly, isolate->factory()->SuspendError_string(),
Context::WASM_SUSPEND_ERROR_FUNCTION_INDEX);
return true;
}
void WasmJs::InstallMemoryControl(Isolate* isolate,
DirectHandle<NativeContext> context,
DirectHandle<JSObject> webassembly) {
// Extensibility of the `WebAssembly` object should already have been checked
// by the caller.
DCHECK(webassembly->map()->is_extensible());
DirectHandle<JSFunction> descriptor_constructor =
InstallConstructorFunc(isolate, webassembly, "MemoryMapDescriptor",
wasm::WebAssemblyMemoryMapDescriptor);
SetupConstructor(
isolate, descriptor_constructor, WASM_MEMORY_MAP_DESCRIPTOR_TYPE,
WasmMemoryMapDescriptor::kHeaderSize, "WebAssembly.MemoryMapDescriptor");
context->set_wasm_memory_map_descriptor_constructor(*descriptor_constructor);
DirectHandle<JSObject> descriptor_proto = direct_handle(
Cast<JSObject>(descriptor_constructor->instance_prototype()), isolate);
InstallFunc(isolate, descriptor_proto, "map",
wasm::WebAssemblyMemoryMapDescriptorMap, 2);
InstallFunc(isolate, descriptor_proto, "unmap",
wasm::WebAssemblyMemoryMapDescriptorUnmap, 0);
}
// Return true only if this call resulted in installation of type reflection.
// static
bool WasmJs::InstallTypeReflection(Isolate* isolate,
DirectHandle<NativeContext> context,
DirectHandle<JSObject> webassembly) {
// Extensibility of the `WebAssembly` object should already have been checked
// by the caller.
DCHECK(webassembly->map()->is_extensible());
// First check if any of the type reflection fields already exist. If so, bail
// out and don't install any new fields.
if (JSObject::HasRealNamedProperty(isolate, webassembly,
isolate->factory()->Function_string())
.FromMaybe(true)) {
return false;
}
auto GetProto = [isolate](Tagged<JSFunction> constructor) {
return handle(Cast<JSObject>(constructor->instance_prototype()), isolate);
};
DirectHandle<JSObject> table_proto =
GetProto(context->wasm_table_constructor());
DirectHandle<JSObject> global_proto =
GetProto(context->wasm_global_constructor());
DirectHandle<JSObject> memory_proto =
GetProto(context->wasm_memory_constructor());
DirectHandle<JSObject> tag_proto = GetProto(context->wasm_tag_constructor());
DirectHandle<String> type_string = v8_str(isolate, "type");
auto CheckProto = [isolate, type_string](DirectHandle<JSObject> proto) {
if (JSObject::HasRealNamedProperty(isolate, proto, type_string)
.FromMaybe(true)) {
return false;
}
// Also check extensibility, otherwise adding properties will fail.
if (!proto->map()->is_extensible()) return false;
return true;
};
if (!CheckProto(table_proto)) return false;
if (!CheckProto(global_proto)) return false;
if (!CheckProto(memory_proto)) return false;
if (!CheckProto(tag_proto)) return false;
// Checks are done, start installing the new fields.
InstallFunc(isolate, table_proto, type_string, WebAssemblyTableType, 0, false,
NONE, SideEffectType::kHasNoSideEffect);
InstallFunc(isolate, memory_proto, type_string, WebAssemblyMemoryType, 0,
false, NONE, SideEffectType::kHasNoSideEffect);
InstallFunc(isolate, global_proto, type_string, WebAssemblyGlobalType, 0,
false, NONE, SideEffectType::kHasNoSideEffect);
InstallFunc(isolate, tag_proto, type_string, WebAssemblyTagType, 0, false,
NONE, SideEffectType::kHasNoSideEffect);
// Create the Function object.
DirectHandle<JSFunction> function_constructor = InstallConstructorFunc(
isolate, webassembly, "Function", WebAssemblyFunction);
SetDummyInstanceTemplate(isolate, function_constructor);
JSFunction::EnsureHasInitialMap(function_constructor);
DirectHandle<JSObject> function_proto(
Cast<JSObject>(function_constructor->instance_prototype()), isolate);
DirectHandle<Map> function_map =
Map::Copy(isolate, isolate->sloppy_function_without_prototype_map(),
"WebAssembly.Function");
CHECK(JSObject::SetPrototype(
isolate, function_proto,
direct_handle(context->function_function()->prototype(), isolate),
false, kDontThrow)
.FromJust());
JSFunction::SetInitialMap(isolate, function_constructor, function_map,
function_proto);
constexpr PropertyAttributes ro_attributes =
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY);
JSObject::AddProperty(isolate, function_proto,
isolate->factory()->to_string_tag_symbol(),
v8_str(isolate, "WebAssembly.Function"), ro_attributes);
InstallFunc(isolate, function_proto, type_string, WebAssemblyFunctionType, 0);
SimpleInstallFunction(isolate, function_proto, "bind",
Builtin::kWebAssemblyFunctionPrototypeBind, 1,
kDontAdapt);
// Make all exported functions an instance of {WebAssembly.Function}.
context->set_wasm_exported_function_map(*function_map);
return true;
}
namespace wasm {
// static
std::unique_ptr<WasmStreaming> StartStreamingForTesting(
Isolate* isolate,
std::shared_ptr<wasm::CompilationResultResolver> resolver) {
return std::make_unique<WasmStreaming>(
std::make_unique<WasmStreaming::WasmStreamingImpl>(
isolate, "StartStreamingForTesting", CompileTimeImports{}, resolver));
}
} // namespace wasm
#undef ASSIGN
#undef EXTRACT_THIS
} // namespace internal
} // namespace v8