Mypal/js/src/wasm/WasmJS.cpp

2059 lines
59 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmJS.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Maybe.h"
#include "jsprf.h"
#include "builtin/Promise.h"
#include "jit/JitOptions.h"
#include "vm/Interpreter.h"
#include "vm/String.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmSignalHandlers.h"
#include "jsobjinlines.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::CheckedInt;
using mozilla::IsNaN;
using mozilla::IsSame;
using mozilla::Nothing;
bool
wasm::HasCompilerSupport(ExclusiveContext* cx)
{
if (gc::SystemPageSize() > wasm::PageSize)
return false;
if (!cx->jitSupportsFloatingPoint())
return false;
if (!cx->jitSupportsUnalignedAccesses())
return false;
if (!wasm::HaveSignalHandlers())
return false;
#if defined(JS_CODEGEN_ARM)
// movw/t are required for the loadWasmActivationFromSymbolicAddress in
// GenerateProfilingPrologue/Epilogue to avoid using the constant pool.
if (!HasMOVWT())
return false;
#endif
#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
return false;
#else
return true;
#endif
}
bool
wasm::HasSupport(ExclusiveContext* cx)
{
return cx->options().wasm() && HasCompilerSupport(cx);
}
// ============================================================================
// Imports
template<typename T>
JSObject*
js::wasm::CreateCustomNaNObject(JSContext* cx, T* addr)
{
MOZ_ASSERT(IsNaN(*addr));
RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj)
return nullptr;
int32_t* i32 = (int32_t*)addr;
RootedValue intVal(cx, Int32Value(i32[0]));
if (!JS_DefineProperty(cx, obj, "nan_low", intVal, JSPROP_ENUMERATE))
return nullptr;
if (IsSame<double, T>::value) {
intVal = Int32Value(i32[1]);
if (!JS_DefineProperty(cx, obj, "nan_high", intVal, JSPROP_ENUMERATE))
return nullptr;
}
return obj;
}
template JSObject* js::wasm::CreateCustomNaNObject(JSContext* cx, float* addr);
template JSObject* js::wasm::CreateCustomNaNObject(JSContext* cx, double* addr);
bool
js::wasm::ReadCustomFloat32NaNObject(JSContext* cx, HandleValue v, uint32_t* ret)
{
RootedObject obj(cx, &v.toObject());
RootedValue val(cx);
int32_t i32;
if (!JS_GetProperty(cx, obj, "nan_low", &val))
return false;
if (!ToInt32(cx, val, &i32))
return false;
*ret = i32;
return true;
}
bool
js::wasm::ReadCustomDoubleNaNObject(JSContext* cx, HandleValue v, uint64_t* ret)
{
RootedObject obj(cx, &v.toObject());
RootedValue val(cx);
int32_t i32;
if (!JS_GetProperty(cx, obj, "nan_high", &val))
return false;
if (!ToInt32(cx, val, &i32))
return false;
*ret = uint32_t(i32);
*ret <<= 32;
if (!JS_GetProperty(cx, obj, "nan_low", &val))
return false;
if (!ToInt32(cx, val, &i32))
return false;
*ret |= uint32_t(i32);
return true;
}
JSObject*
wasm::CreateI64Object(JSContext* cx, int64_t i64)
{
RootedObject result(cx, JS_NewPlainObject(cx));
if (!result)
return nullptr;
RootedValue val(cx, Int32Value(uint32_t(i64)));
if (!JS_DefineProperty(cx, result, "low", val, JSPROP_ENUMERATE))
return nullptr;
val = Int32Value(uint32_t(i64 >> 32));
if (!JS_DefineProperty(cx, result, "high", val, JSPROP_ENUMERATE))
return nullptr;
return result;
}
bool
wasm::ReadI64Object(JSContext* cx, HandleValue v, int64_t* i64)
{
if (!v.isObject()) {
JS_ReportErrorASCII(cx, "i64 JS value must be an object");
return false;
}
RootedObject obj(cx, &v.toObject());
int32_t* i32 = (int32_t*)i64;
RootedValue val(cx);
if (!JS_GetProperty(cx, obj, "low", &val))
return false;
if (!ToInt32(cx, val, &i32[0]))
return false;
if (!JS_GetProperty(cx, obj, "high", &val))
return false;
if (!ToInt32(cx, val, &i32[1]))
return false;
return true;
}
static bool
ThrowBadImportArg(JSContext* cx)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_ARG);
return false;
}
static bool
ThrowBadImportField(JSContext* cx, const char* field, const char* str)
{
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_FIELD, field, str);
return false;
}
static bool
GetProperty(JSContext* cx, HandleObject obj, const char* chars, MutableHandleValue v)
{
JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
if (!atom)
return false;
RootedId id(cx, AtomToId(atom));
return GetProperty(cx, obj, obj, id, v);
}
static bool
GetImports(JSContext* cx,
const Module& module,
HandleObject importObj,
MutableHandle<FunctionVector> funcImports,
MutableHandleWasmTableObject tableImport,
MutableHandleWasmMemoryObject memoryImport,
ValVector* globalImports)
{
const ImportVector& imports = module.imports();
if (!imports.empty() && !importObj)
return ThrowBadImportArg(cx);
const Metadata& metadata = module.metadata();
uint32_t globalIndex = 0;
const GlobalDescVector& globals = metadata.globals;
for (const Import& import : imports) {
RootedValue v(cx);
if (!GetProperty(cx, importObj, import.module.get(), &v))
return false;
if (!v.isObject())
return ThrowBadImportField(cx, import.module.get(), "an Object");
RootedObject obj(cx, &v.toObject());
if (!GetProperty(cx, obj, import.field.get(), &v))
return false;
switch (import.kind) {
case DefinitionKind::Function:
if (!IsFunctionObject(v))
return ThrowBadImportField(cx, import.field.get(), "a Function");
if (!funcImports.append(&v.toObject().as<JSFunction>()))
return false;
break;
case DefinitionKind::Table:
if (!v.isObject() || !v.toObject().is<WasmTableObject>())
return ThrowBadImportField(cx, import.field.get(), "a Table");
MOZ_ASSERT(!tableImport);
tableImport.set(&v.toObject().as<WasmTableObject>());
break;
case DefinitionKind::Memory:
if (!v.isObject() || !v.toObject().is<WasmMemoryObject>())
return ThrowBadImportField(cx, import.field.get(), "a Memory");
MOZ_ASSERT(!memoryImport);
memoryImport.set(&v.toObject().as<WasmMemoryObject>());
break;
case DefinitionKind::Global:
Val val;
const GlobalDesc& global = globals[globalIndex++];
MOZ_ASSERT(global.importIndex() == globalIndex - 1);
MOZ_ASSERT(!global.isMutable());
switch (global.type()) {
case ValType::I32: {
if (!v.isNumber())
return ThrowBadImportField(cx, import.field.get(), "a number");
int32_t i32;
if (!ToInt32(cx, v, &i32))
return false;
val = Val(uint32_t(i32));
break;
}
case ValType::I64: {
MOZ_ASSERT(JitOptions.wasmTestMode, "no int64 in JS");
int64_t i64;
if (!ReadI64Object(cx, v, &i64))
return false;
val = Val(uint64_t(i64));
break;
}
case ValType::F32: {
if (JitOptions.wasmTestMode && v.isObject()) {
uint32_t bits;
if (!ReadCustomFloat32NaNObject(cx, v, &bits))
return false;
val = Val(RawF32::fromBits(bits));
break;
}
if (!v.isNumber())
return ThrowBadImportField(cx, import.field.get(), "a number");
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(RawF32(float(d)));
break;
}
case ValType::F64: {
if (JitOptions.wasmTestMode && v.isObject()) {
uint64_t bits;
if (!ReadCustomDoubleNaNObject(cx, v, &bits))
return false;
val = Val(RawF64::fromBits(bits));
break;
}
if (!v.isNumber())
return ThrowBadImportField(cx, import.field.get(), "a number");
double d;
if (!ToNumber(cx, v, &d))
return false;
val = Val(RawF64(d));
break;
}
default: {
MOZ_CRASH("unexpected import value type");
}
}
if (!globalImports->append(val))
return false;
}
}
MOZ_ASSERT(globalIndex == globals.length() || !globals[globalIndex].isImport());
return true;
}
// ============================================================================
// Fuzzing support
static bool
DescribeScriptedCaller(JSContext* cx, ScriptedCaller* scriptedCaller)
{
// Note: JS::DescribeScriptedCaller returns whether a scripted caller was
// found, not whether an error was thrown. This wrapper function converts
// back to the more ordinary false-if-error form.
JS::AutoFilename af;
if (JS::DescribeScriptedCaller(cx, &af, &scriptedCaller->line, &scriptedCaller->column)) {
scriptedCaller->filename = DuplicateString(cx, af.get());
if (!scriptedCaller->filename)
return false;
}
return true;
}
bool
wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj,
MutableHandleWasmInstanceObject instanceObj)
{
if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_WebAssembly))
return false;
MutableBytes bytecode = cx->new_<ShareableBytes>();
if (!bytecode)
return false;
if (!bytecode->append((uint8_t*)code->viewDataEither().unwrap(), code->byteLength())) {
ReportOutOfMemory(cx);
return false;
}
ScriptedCaller scriptedCaller;
if (!DescribeScriptedCaller(cx, &scriptedCaller))
return false;
CompileArgs compileArgs;
if (!compileArgs.initFromContext(cx, Move(scriptedCaller)))
return false;
UniqueChars error;
SharedModule module = Compile(*bytecode, compileArgs, &error);
if (!module) {
if (error) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
error.get());
return false;
}
ReportOutOfMemory(cx);
return false;
}
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
RootedWasmTableObject table(cx);
RootedWasmMemoryObject memory(cx);
ValVector globals;
if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, &globals))
return false;
return module->instantiate(cx, funcs, table, memory, globals, nullptr, instanceObj);
}
// ============================================================================
// Common functions
static bool
ToNonWrappingUint32(JSContext* cx, HandleValue v, uint32_t max, const char* kind, const char* noun,
uint32_t* u32)
{
double dbl;
if (!ToInteger(cx, v, &dbl))
return false;
if (dbl < 0 || dbl > max) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32,
kind, noun);
return false;
}
*u32 = uint32_t(dbl);
MOZ_ASSERT(double(*u32) == dbl);
return true;
}
static bool
GetLimits(JSContext* cx, HandleObject obj, uint32_t max, const char* kind,
Limits* limits)
{
JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
if (!initialAtom)
return false;
RootedId initialId(cx, AtomToId(initialAtom));
RootedValue initialVal(cx);
if (!GetProperty(cx, obj, obj, initialId, &initialVal))
return false;
if (!ToNonWrappingUint32(cx, initialVal, max, kind, "initial size", &limits->initial))
return false;
JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
if (!maximumAtom)
return false;
RootedId maximumId(cx, AtomToId(maximumAtom));
bool found;
if (HasProperty(cx, obj, maximumId, &found) && found) {
RootedValue maxVal(cx);
if (!GetProperty(cx, obj, obj, maximumId, &maxVal))
return false;
limits->maximum.emplace();
if (!ToNonWrappingUint32(cx, maxVal, max, kind, "maximum size", limits->maximum.ptr()))
return false;
if (limits->initial > *limits->maximum) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32,
kind, "maximum size");
return false;
}
}
return true;
}
// ============================================================================
// WebAssembly.Module class and methods
const ClassOps WasmModuleObject::classOps_ =
{
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WasmModuleObject::finalize
};
const Class WasmModuleObject::class_ =
{
"WebAssembly.Module",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmModuleObject::classOps_,
};
const JSPropertySpec WasmModuleObject::properties[] =
{ JS_PS_END };
const JSFunctionSpec WasmModuleObject::methods[] =
{ JS_FS_END };
const JSFunctionSpec WasmModuleObject::static_methods[] =
{
JS_FN("imports", WasmModuleObject::imports, 1, 0),
JS_FN("exports", WasmModuleObject::exports, 1, 0),
JS_FS_END
};
/* static */ void
WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
{
obj->as<WasmModuleObject>().module().Release();
}
static bool
IsModuleObject(JSObject* obj, Module** module)
{
JSObject* unwrapped = CheckedUnwrap(obj);
if (!unwrapped || !unwrapped->is<WasmModuleObject>())
return false;
*module = &unwrapped->as<WasmModuleObject>().module();
return true;
}
static bool
GetModuleArg(JSContext* cx, CallArgs args, const char* name, Module** module)
{
if (!args.requireAtLeast(cx, name, 1))
return false;
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MOD_ARG);
return false;
}
return true;
}
struct KindNames
{
RootedPropertyName kind;
RootedPropertyName table;
RootedPropertyName memory;
explicit KindNames(JSContext* cx) : kind(cx), table(cx), memory(cx) {}
};
static bool
InitKindNames(JSContext* cx, KindNames* names)
{
JSAtom* kind = Atomize(cx, "kind", strlen("kind"));
if (!kind)
return false;
names->kind = kind->asPropertyName();
JSAtom* table = Atomize(cx, "table", strlen("table"));
if (!table)
return false;
names->table = table->asPropertyName();
JSAtom* memory = Atomize(cx, "memory", strlen("memory"));
if (!memory)
return false;
names->memory = memory->asPropertyName();
return true;
}
static JSString*
KindToString(JSContext* cx, const KindNames& names, DefinitionKind kind)
{
switch (kind) {
case DefinitionKind::Function:
return cx->names().function;
case DefinitionKind::Table:
return names.table;
case DefinitionKind::Memory:
return names.memory;
case DefinitionKind::Global:
return cx->names().global;
}
MOZ_CRASH("invalid kind");
}
static JSString*
UTF8CharsToString(JSContext* cx, const char* chars)
{
return NewStringCopyUTF8Z<CanGC>(cx, JS::ConstUTF8CharsZ(chars, strlen(chars)));
}
/* static */ bool
WasmModuleObject::imports(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Module* module;
if (!GetModuleArg(cx, args, "WebAssembly.Module.imports", &module))
return false;
KindNames names(cx);
if (!InitKindNames(cx, &names))
return false;
AutoValueVector elems(cx);
if (!elems.reserve(module->imports().length()))
return false;
for (const Import& import : module->imports()) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (!props.reserve(3))
return false;
JSString* moduleStr = UTF8CharsToString(cx, import.module.get());
if (!moduleStr)
return false;
props.infallibleAppend(IdValuePair(NameToId(cx->names().module), StringValue(moduleStr)));
JSString* nameStr = UTF8CharsToString(cx, import.field.get());
if (!nameStr)
return false;
props.infallibleAppend(IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
JSString* kindStr = KindToString(cx, names, import.kind);
if (!kindStr)
return false;
props.infallibleAppend(IdValuePair(NameToId(names.kind), StringValue(kindStr)));
JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(), props.length(), GenericObject);
if (!obj)
return false;
elems.infallibleAppend(ObjectValue(*obj));
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr)
return false;
args.rval().setObject(*arr);
return true;
}
/* static */ bool
WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Module* module;
if (!GetModuleArg(cx, args, "WebAssembly.Module.exports", &module))
return false;
KindNames names(cx);
if (!InitKindNames(cx, &names))
return false;
AutoValueVector elems(cx);
if (!elems.reserve(module->exports().length()))
return false;
for (const Export& exp : module->exports()) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (!props.reserve(2))
return false;
JSString* nameStr = UTF8CharsToString(cx, exp.fieldName());
if (!nameStr)
return false;
props.infallibleAppend(IdValuePair(NameToId(cx->names().name), StringValue(nameStr)));
JSString* kindStr = KindToString(cx, names, exp.kind());
if (!kindStr)
return false;
props.infallibleAppend(IdValuePair(NameToId(names.kind), StringValue(kindStr)));
JSObject* obj = ObjectGroup::newPlainObject(cx, props.begin(), props.length(), GenericObject);
if (!obj)
return false;
elems.infallibleAppend(ObjectValue(*obj));
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr)
return false;
args.rval().setObject(*arr);
return true;
}
/* static */ WasmModuleObject*
WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto)
{
AutoSetNewObjectMetadata metadata(cx);
auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
if (!obj)
return nullptr;
obj->initReservedSlot(MODULE_SLOT, PrivateValue(&module));
module.AddRef();
return obj;
}
static bool
GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber, MutableBytes* bytecode)
{
*bytecode = cx->new_<ShareableBytes>();
if (!*bytecode)
return false;
JSObject* unwrapped = CheckedUnwrap(obj);
size_t byteLength = 0;
uint8_t* ptr = nullptr;
if (unwrapped && unwrapped->is<TypedArrayObject>()) {
TypedArrayObject& view = unwrapped->as<TypedArrayObject>();
byteLength = view.byteLength();
ptr = (uint8_t*)view.viewDataEither().unwrap();
} else if (unwrapped && unwrapped->is<ArrayBufferObject>()) {
ArrayBufferObject& buffer = unwrapped->as<ArrayBufferObject>();
byteLength = buffer.byteLength();
ptr = buffer.dataPointer();
} else {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber);
return false;
}
if (!(*bytecode)->append(ptr, byteLength)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
static bool
InitCompileArgs(JSContext* cx, CompileArgs* compileArgs)
{
ScriptedCaller scriptedCaller;
if (!DescribeScriptedCaller(cx, &scriptedCaller))
return false;
return compileArgs->initFromContext(cx, Move(scriptedCaller));
}
/* static */ bool
WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs callArgs = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, callArgs, "Module"))
return false;
if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1))
return false;
if (!callArgs[0].isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
return false;
}
MutableBytes bytecode;
if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, &bytecode))
return false;
CompileArgs compileArgs;
if (!InitCompileArgs(cx, &compileArgs))
return false;
UniqueChars error;
SharedModule module = Compile(*bytecode, compileArgs, &error);
if (!module) {
if (error) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
error.get());
return false;
}
ReportOutOfMemory(cx);
return false;
}
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
if (!moduleObj)
return false;
callArgs.rval().setObject(*moduleObj);
return true;
}
Module&
WasmModuleObject::module() const
{
MOZ_ASSERT(is<WasmModuleObject>());
return *(Module*)getReservedSlot(MODULE_SLOT).toPrivate();
}
// ============================================================================
// WebAssembly.Instance class and methods
const ClassOps WasmInstanceObject::classOps_ =
{
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WasmInstanceObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
WasmInstanceObject::trace
};
const Class WasmInstanceObject::class_ =
{
"WebAssembly.Instance",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmInstanceObject::classOps_,
};
const JSPropertySpec WasmInstanceObject::properties[] =
{ JS_PS_END };
const JSFunctionSpec WasmInstanceObject::methods[] =
{ JS_FS_END };
const JSFunctionSpec WasmInstanceObject::static_methods[] =
{ JS_FS_END };
bool
WasmInstanceObject::isNewborn() const
{
MOZ_ASSERT(is<WasmInstanceObject>());
return getReservedSlot(INSTANCE_SLOT).isUndefined();
}
/* static */ void
WasmInstanceObject::finalize(FreeOp* fop, JSObject* obj)
{
fop->delete_(&obj->as<WasmInstanceObject>().exports());
if (!obj->as<WasmInstanceObject>().isNewborn())
fop->delete_(&obj->as<WasmInstanceObject>().instance());
}
/* static */ void
WasmInstanceObject::trace(JSTracer* trc, JSObject* obj)
{
if (!obj->as<WasmInstanceObject>().isNewborn())
obj->as<WasmInstanceObject>().instance().tracePrivate(trc);
}
/* static */ WasmInstanceObject*
WasmInstanceObject::create(JSContext* cx,
UniqueCode code,
HandleWasmMemoryObject memory,
SharedTableVector&& tables,
Handle<FunctionVector> funcImports,
const ValVector& globalImports,
HandleObject proto)
{
UniquePtr<WeakExportMap> exports = js::MakeUnique<WeakExportMap>(cx->zone(), ExportMap());
if (!exports || !exports->init()) {
ReportOutOfMemory(cx);
return nullptr;
}
AutoSetNewObjectMetadata metadata(cx);
RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto<WasmInstanceObject>(cx, proto));
if (!obj)
return nullptr;
obj->setReservedSlot(EXPORTS_SLOT, PrivateValue(exports.release()));
MOZ_ASSERT(obj->isNewborn());
MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
// Root the Instance via WasmInstanceObject before any possible GC.
auto* instance = cx->new_<Instance>(cx,
obj,
Move(code),
memory,
Move(tables),
funcImports,
globalImports);
if (!instance)
return nullptr;
obj->initReservedSlot(INSTANCE_SLOT, PrivateValue(instance));
MOZ_ASSERT(!obj->isNewborn());
if (!instance->init(cx))
return nullptr;
return obj;
}
static bool
Instantiate(JSContext* cx, const Module& module, HandleObject importObj,
MutableHandleWasmInstanceObject instanceObj)
{
RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
RootedWasmTableObject table(cx);
RootedWasmMemoryObject memory(cx);
ValVector globals;
if (!GetImports(cx, module, importObj, &funcs, &table, &memory, &globals))
return false;
return module.instantiate(cx, funcs, table, memory, globals, instanceProto, instanceObj);
}
/* static */ bool
WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "Instance"))
return false;
if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1))
return false;
Module* module;
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MOD_ARG);
return false;
}
RootedObject importObj(cx);
if (!args.get(1).isUndefined()) {
if (!args[1].isObject())
return ThrowBadImportArg(cx);
importObj = &args[1].toObject();
}
RootedWasmInstanceObject instanceObj(cx);
if (!Instantiate(cx, *module, importObj, &instanceObj))
return false;
args.rval().setObject(*instanceObj);
return true;
}
Instance&
WasmInstanceObject::instance() const
{
MOZ_ASSERT(!isNewborn());
return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
}
WasmInstanceObject::WeakExportMap&
WasmInstanceObject::exports() const
{
return *(WeakExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
}
static bool
WasmCall(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction callee(cx, &args.callee().as<JSFunction>());
Instance& instance = ExportedFunctionToInstance(callee);
uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
return instance.callExport(cx, funcIndex, args);
}
/* static */ bool
WasmInstanceObject::getExportedFunction(JSContext* cx, HandleWasmInstanceObject instanceObj,
uint32_t funcIndex, MutableHandleFunction fun)
{
if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) {
fun.set(p->value());
return true;
}
const Instance& instance = instanceObj->instance();
unsigned numArgs = instance.metadata().lookupFuncExport(funcIndex).sig().args().length();
// asm.js needs to act like a normal JS function which means having the name
// from the original source and being callable as a constructor.
if (instance.isAsmJS()) {
RootedAtom name(cx, instance.code().getFuncAtom(cx, funcIndex));
if (!name)
return false;
fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED,
SingletonObject, JSFunction::ASMJS_CTOR));
if (!fun)
return false;
} else {
RootedAtom name(cx, NumberToAtom(cx, funcIndex));
if (!name)
return false;
fun.set(NewNativeFunction(cx, WasmCall, numArgs, name, gc::AllocKind::FUNCTION_EXTENDED));
if (!fun)
return false;
}
fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT, ObjectValue(*instanceObj));
fun->setExtendedSlot(FunctionExtended::WASM_FUNC_INDEX_SLOT, Int32Value(funcIndex));
if (!instanceObj->exports().putNew(funcIndex, fun)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
const CodeRange&
WasmInstanceObject::getExportedFunctionCodeRange(HandleFunction fun)
{
uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun);
const Metadata& metadata = instance().metadata();
return metadata.codeRanges[metadata.lookupFuncExport(funcIndex).codeRangeIndex()];
}
bool
wasm::IsExportedFunction(JSFunction* fun)
{
return fun->maybeNative() == WasmCall;
}
bool
wasm::IsExportedWasmFunction(JSFunction* fun)
{
return IsExportedFunction(fun) && !ExportedFunctionToInstance(fun).isAsmJS();
}
bool
wasm::IsExportedFunction(const Value& v, MutableHandleFunction f)
{
if (!v.isObject())
return false;
JSObject& obj = v.toObject();
if (!obj.is<JSFunction>() || !IsExportedFunction(&obj.as<JSFunction>()))
return false;
f.set(&obj.as<JSFunction>());
return true;
}
Instance&
wasm::ExportedFunctionToInstance(JSFunction* fun)
{
return ExportedFunctionToInstanceObject(fun)->instance();
}
WasmInstanceObject*
wasm::ExportedFunctionToInstanceObject(JSFunction* fun)
{
MOZ_ASSERT(IsExportedFunction(fun));
const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT);
return &v.toObject().as<WasmInstanceObject>();
}
uint32_t
wasm::ExportedFunctionToFuncIndex(JSFunction* fun)
{
MOZ_ASSERT(IsExportedFunction(fun));
const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_FUNC_INDEX_SLOT);
return v.toInt32();
}
// ============================================================================
// WebAssembly.Memory class and methods
const ClassOps WasmMemoryObject::classOps_ =
{
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WasmMemoryObject::finalize
};
const Class WasmMemoryObject::class_ =
{
"WebAssembly.Memory",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmMemoryObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmMemoryObject::classOps_
};
/* static */ void
WasmMemoryObject::finalize(FreeOp* fop, JSObject* obj)
{
WasmMemoryObject& memory = obj->as<WasmMemoryObject>();
if (memory.hasObservers())
fop->delete_(&memory.observers());
}
/* static */ WasmMemoryObject*
WasmMemoryObject::create(ExclusiveContext* cx, HandleArrayBufferObjectMaybeShared buffer,
HandleObject proto)
{
AutoSetNewObjectMetadata metadata(cx);
auto* obj = NewObjectWithGivenProto<WasmMemoryObject>(cx, proto);
if (!obj)
return nullptr;
obj->initReservedSlot(BUFFER_SLOT, ObjectValue(*buffer));
MOZ_ASSERT(!obj->hasObservers());
return obj;
}
/* static */ bool
WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "Memory"))
return false;
if (!args.requireAtLeast(cx, "WebAssembly.Memory", 1))
return false;
if (!args.get(0).isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "memory");
return false;
}
RootedObject obj(cx, &args[0].toObject());
Limits limits;
if (!GetLimits(cx, obj, UINT32_MAX / PageSize, "Memory", &limits))
return false;
limits.initial *= PageSize;
if (limits.maximum)
limits.maximum = Some(*limits.maximum * PageSize);
RootedArrayBufferObject buffer(cx,
ArrayBufferObject::createForWasm(cx, limits.initial, limits.maximum));
if (!buffer)
return false;
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmMemory).toObject());
RootedWasmMemoryObject memoryObj(cx, WasmMemoryObject::create(cx, buffer, proto));
if (!memoryObj)
return false;
args.rval().setObject(*memoryObj);
return true;
}
static bool
IsMemory(HandleValue v)
{
return v.isObject() && v.toObject().is<WasmMemoryObject>();
}
/* static */ bool
WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args)
{
args.rval().setObject(args.thisv().toObject().as<WasmMemoryObject>().buffer());
return true;
}
/* static */ bool
WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args);
}
const JSPropertySpec WasmMemoryObject::properties[] =
{
JS_PSG("buffer", WasmMemoryObject::bufferGetter, 0),
JS_PS_END
};
/* static */ bool
WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args)
{
RootedWasmMemoryObject memory(cx, &args.thisv().toObject().as<WasmMemoryObject>());
uint32_t delta;
if (!ToNonWrappingUint32(cx, args.get(0), UINT32_MAX, "Memory", "grow delta", &delta))
return false;
uint32_t ret = grow(memory, delta, cx);
if (ret == uint32_t(-1)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "memory");
return false;
}
args.rval().setInt32(ret);
return true;
}
/* static */ bool
WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsMemory, growImpl>(cx, args);
}
const JSFunctionSpec WasmMemoryObject::methods[] =
{
JS_FN("grow", WasmMemoryObject::grow, 1, 0),
JS_FS_END
};
const JSFunctionSpec WasmMemoryObject::static_methods[] =
{ JS_FS_END };
ArrayBufferObjectMaybeShared&
WasmMemoryObject::buffer() const
{
return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObjectMaybeShared>();
}
bool
WasmMemoryObject::hasObservers() const
{
return !getReservedSlot(OBSERVERS_SLOT).isUndefined();
}
WasmMemoryObject::WeakInstanceSet&
WasmMemoryObject::observers() const
{
MOZ_ASSERT(hasObservers());
return *reinterpret_cast<WeakInstanceSet*>(getReservedSlot(OBSERVERS_SLOT).toPrivate());
}
WasmMemoryObject::WeakInstanceSet*
WasmMemoryObject::getOrCreateObservers(JSContext* cx)
{
if (!hasObservers()) {
auto observers = MakeUnique<WeakInstanceSet>(cx->zone(), InstanceSet());
if (!observers || !observers->init()) {
ReportOutOfMemory(cx);
return nullptr;
}
setReservedSlot(OBSERVERS_SLOT, PrivateValue(observers.release()));
}
return &observers();
}
bool
WasmMemoryObject::movingGrowable() const
{
#ifdef WASM_HUGE_MEMORY
return false;
#else
return !buffer().wasmMaxSize();
#endif
}
bool
WasmMemoryObject::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
{
MOZ_ASSERT(movingGrowable());
WeakInstanceSet* observers = getOrCreateObservers(cx);
if (!observers)
return false;
if (!observers->putNew(instance)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
/* static */ uint32_t
WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta, JSContext* cx)
{
RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
MOZ_ASSERT(oldBuf->byteLength() % PageSize == 0);
uint32_t oldNumPages = oldBuf->byteLength() / PageSize;
CheckedInt<uint32_t> newSize = oldNumPages;
newSize += delta;
newSize *= PageSize;
if (!newSize.isValid())
return -1;
RootedArrayBufferObject newBuf(cx);
uint8_t* prevMemoryBase = nullptr;
if (Maybe<uint32_t> maxSize = oldBuf->wasmMaxSize()) {
if (newSize.value() > maxSize.value())
return -1;
if (!ArrayBufferObject::wasmGrowToSizeInPlace(newSize.value(), oldBuf, &newBuf, cx))
return -1;
} else {
#ifdef WASM_HUGE_MEMORY
if (!ArrayBufferObject::wasmGrowToSizeInPlace(newSize.value(), oldBuf, &newBuf, cx))
return -1;
#else
MOZ_ASSERT(memory->movingGrowable());
prevMemoryBase = oldBuf->dataPointer();
if (!ArrayBufferObject::wasmMovingGrowToSize(newSize.value(), oldBuf, &newBuf, cx))
return -1;
#endif
}
memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
// Only notify moving-grow-observers after the BUFFER_SLOT has been updated
// since observers will call buffer().
if (memory->hasObservers()) {
MOZ_ASSERT(prevMemoryBase);
for (InstanceSet::Range r = memory->observers().all(); !r.empty(); r.popFront())
r.front()->instance().onMovingGrowMemory(prevMemoryBase);
}
return oldNumPages;
}
// ============================================================================
// WebAssembly.Table class and methods
const ClassOps WasmTableObject::classOps_ =
{
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WasmTableObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
WasmTableObject::trace
};
const Class WasmTableObject::class_ =
{
"WebAssembly.Table",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmTableObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmTableObject::classOps_
};
bool
WasmTableObject::isNewborn() const
{
MOZ_ASSERT(is<WasmTableObject>());
return getReservedSlot(TABLE_SLOT).isUndefined();
}
/* static */ void
WasmTableObject::finalize(FreeOp* fop, JSObject* obj)
{
WasmTableObject& tableObj = obj->as<WasmTableObject>();
if (!tableObj.isNewborn())
tableObj.table().Release();
}
/* static */ void
WasmTableObject::trace(JSTracer* trc, JSObject* obj)
{
WasmTableObject& tableObj = obj->as<WasmTableObject>();
if (!tableObj.isNewborn())
tableObj.table().tracePrivate(trc);
}
/* static */ WasmTableObject*
WasmTableObject::create(JSContext* cx, Limits limits)
{
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
AutoSetNewObjectMetadata metadata(cx);
RootedWasmTableObject obj(cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
if (!obj)
return nullptr;
MOZ_ASSERT(obj->isNewborn());
TableDesc td(TableKind::AnyFunction, limits);
td.external = true;
SharedTable table = Table::create(cx, td, obj);
if (!table)
return nullptr;
obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take()));
MOZ_ASSERT(!obj->isNewborn());
return obj;
}
/* static */ bool
WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "Table"))
return false;
if (!args.requireAtLeast(cx, "WebAssembly.Table", 1))
return false;
if (!args.get(0).isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "table");
return false;
}
RootedObject obj(cx, &args[0].toObject());
JSAtom* elementAtom = Atomize(cx, "element", strlen("element"));
if (!elementAtom)
return false;
RootedId elementId(cx, AtomToId(elementAtom));
RootedValue elementVal(cx);
if (!GetProperty(cx, obj, obj, elementId, &elementVal))
return false;
if (!elementVal.isString()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
return false;
}
JSLinearString* elementStr = elementVal.toString()->ensureLinear(cx);
if (!elementStr)
return false;
if (!StringEqualsAscii(elementStr, "anyfunc")) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
return false;
}
Limits limits;
if (!GetLimits(cx, obj, UINT32_MAX, "Table", &limits))
return false;
RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits));
if (!table)
return false;
args.rval().setObject(*table);
return true;
}
static bool
IsTable(HandleValue v)
{
return v.isObject() && v.toObject().is<WasmTableObject>();
}
/* static */ bool
WasmTableObject::lengthGetterImpl(JSContext* cx, const CallArgs& args)
{
args.rval().setNumber(args.thisv().toObject().as<WasmTableObject>().table().length());
return true;
}
/* static */ bool
WasmTableObject::lengthGetter(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsTable, lengthGetterImpl>(cx, args);
}
const JSPropertySpec WasmTableObject::properties[] =
{
JS_PSG("length", WasmTableObject::lengthGetter, 0),
JS_PS_END
};
static bool
ToTableIndex(JSContext* cx, HandleValue v, const Table& table, const char* noun, uint32_t* index)
{
if (!ToNonWrappingUint32(cx, v, UINT32_MAX, "Table", noun, index))
return false;
if (*index >= table.length()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32, "Table", noun);
return false;
}
return true;
}
/* static */ bool
WasmTableObject::getImpl(JSContext* cx, const CallArgs& args)
{
RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>());
const Table& table = tableObj->table();
uint32_t index;
if (!ToTableIndex(cx, args.get(0), table, "get index", &index))
return false;
ExternalTableElem& elem = table.externalArray()[index];
if (!elem.code) {
args.rval().setNull();
return true;
}
Instance& instance = *elem.tls->instance;
const CodeRange& codeRange = *instance.code().lookupRange(elem.code);
MOZ_ASSERT(codeRange.isFunction());
RootedWasmInstanceObject instanceObj(cx, instance.object());
RootedFunction fun(cx);
if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcIndex(), &fun))
return false;
args.rval().setObject(*fun);
return true;
}
/* static */ bool
WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsTable, getImpl>(cx, args);
}
/* static */ bool
WasmTableObject::setImpl(JSContext* cx, const CallArgs& args)
{
RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>());
Table& table = tableObj->table();
if (!args.requireAtLeast(cx, "set", 2))
return false;
uint32_t index;
if (!ToTableIndex(cx, args.get(0), table, "set index", &index))
return false;
RootedFunction value(cx);
if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
return false;
}
if (value) {
RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
#ifdef DEBUG
RootedFunction f(cx);
MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
MOZ_ASSERT(value == f);
#endif
Instance& instance = instanceObj->instance();
const FuncExport& funcExport = instance.metadata().lookupFuncExport(funcIndex);
const CodeRange& codeRange = instance.metadata().codeRanges[funcExport.codeRangeIndex()];
void* code = instance.codeSegment().base() + codeRange.funcTableEntry();
table.set(index, code, instance);
} else {
table.setNull(index);
}
args.rval().setUndefined();
return true;
}
/* static */ bool
WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsTable, setImpl>(cx, args);
}
/* static */ bool
WasmTableObject::growImpl(JSContext* cx, const CallArgs& args)
{
RootedWasmTableObject table(cx, &args.thisv().toObject().as<WasmTableObject>());
uint32_t delta;
if (!ToNonWrappingUint32(cx, args.get(0), UINT32_MAX, "Table", "grow delta", &delta))
return false;
uint32_t ret = table->table().grow(delta, cx);
if (ret == uint32_t(-1)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW, "table");
return false;
}
args.rval().setInt32(ret);
return true;
}
/* static */ bool
WasmTableObject::grow(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsTable, growImpl>(cx, args);
}
const JSFunctionSpec WasmTableObject::methods[] =
{
JS_FN("get", WasmTableObject::get, 1, 0),
JS_FN("set", WasmTableObject::set, 2, 0),
JS_FN("grow", WasmTableObject::grow, 1, 0),
JS_FS_END
};
const JSFunctionSpec WasmTableObject::static_methods[] =
{ JS_FS_END };
Table&
WasmTableObject::table() const
{
return *(Table*)getReservedSlot(TABLE_SLOT).toPrivate();
}
// ============================================================================
// WebAssembly class and static methods
#if JS_HAS_TOSOURCE
static bool
WebAssembly_toSource(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().WebAssembly);
return true;
}
#endif
static bool
Nop(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
return true;
}
static bool
Reject(JSContext* cx, const CompileArgs& args, UniqueChars error, Handle<PromiseObject*> promise)
{
if (!error) {
ReportOutOfMemory(cx);
RootedValue rejectionValue(cx);
if (!cx->getPendingException(&rejectionValue))
return false;
return PromiseObject::reject(cx, promise, rejectionValue);
}
RootedObject stack(cx, promise->allocationSite());
RootedString filename(cx, JS_NewStringCopyZ(cx, args.scriptedCaller.filename.get()));
if (!filename)
return false;
unsigned line = args.scriptedCaller.line;
unsigned column = args.scriptedCaller.column;
// Ideally we'd report a JSMSG_WASM_COMPILE_ERROR here, but there's no easy
// way to create an ErrorObject for an arbitrary error code with multiple
// replacements.
UniqueChars str(JS_smprintf("wasm validation error: %s", error.get()));
if (!str)
return false;
RootedString message(cx, NewLatin1StringZ(cx, Move(str)));
if (!message)
return false;
RootedObject errorObj(cx,
ErrorObject::create(cx, JSEXN_WASMCOMPILEERROR, stack, filename, line, column, nullptr, message));
if (!errorObj)
return false;
RootedValue rejectionValue(cx, ObjectValue(*errorObj));
return PromiseObject::reject(cx, promise, rejectionValue);
}
static bool
ResolveCompilation(JSContext* cx, Module& module, Handle<PromiseObject*> promise)
{
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
if (!moduleObj)
return false;
RootedValue resolutionValue(cx, ObjectValue(*moduleObj));
return PromiseObject::resolve(cx, promise, resolutionValue);
}
struct CompileTask : PromiseTask
{
MutableBytes bytecode;
CompileArgs compileArgs;
UniqueChars error;
SharedModule module;
CompileTask(JSContext* cx, Handle<PromiseObject*> promise)
: PromiseTask(cx, promise)
{}
void execute() override {
module = Compile(*bytecode, compileArgs, &error);
}
bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
return module
? ResolveCompilation(cx, *module, promise)
: Reject(cx, compileArgs, Move(error), promise);
}
};
static bool
RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise)
{
if (!cx->isExceptionPending())
return false;
RootedValue rejectionValue(cx);
if (!GetAndClearException(cx, &rejectionValue))
return false;
return PromiseObject::reject(cx, promise, rejectionValue);
}
static bool
RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise, CallArgs& callArgs)
{
if (!RejectWithPendingException(cx, promise))
return false;
callArgs.rval().setObject(*promise);
return true;
}
static bool
GetBufferSource(JSContext* cx, CallArgs callArgs, const char* name, MutableBytes* bytecode)
{
if (!callArgs.requireAtLeast(cx, name, 1))
return false;
if (!callArgs[0].isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_ARG);
return false;
}
return GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, bytecode);
}
static bool
WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp)
{
if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback) {
JS_ReportErrorASCII(cx, "WebAssembly.compile not supported in this runtime.");
return false;
}
RootedFunction nopFun(cx, NewNativeFunction(cx, Nop, 0, nullptr));
if (!nopFun)
return false;
Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, nopFun));
if (!promise)
return false;
auto task = cx->make_unique<CompileTask>(cx, promise);
if (!task)
return false;
CallArgs callArgs = CallArgsFromVp(argc, vp);
if (!GetBufferSource(cx, callArgs, "WebAssembly.compile", &task->bytecode))
return RejectWithPendingException(cx, promise, callArgs);
if (!InitCompileArgs(cx, &task->compileArgs))
return false;
if (!StartPromiseTask(cx, Move(task)))
return false;
callArgs.rval().setObject(*promise);
return true;
}
static bool
ResolveInstantiation(JSContext* cx, Module& module, HandleObject importObj,
Handle<PromiseObject*> promise)
{
RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
if (!moduleObj)
return false;
RootedWasmInstanceObject instanceObj(cx);
if (!Instantiate(cx, module, importObj, &instanceObj))
return RejectWithPendingException(cx, promise);
RootedObject resultObj(cx, JS_NewPlainObject(cx));
if (!resultObj)
return false;
RootedValue val(cx, ObjectValue(*moduleObj));
if (!JS_DefineProperty(cx, resultObj, "module", val, JSPROP_ENUMERATE))
return false;
val = ObjectValue(*instanceObj);
if (!JS_DefineProperty(cx, resultObj, "instance", val, JSPROP_ENUMERATE))
return false;
val = ObjectValue(*resultObj);
return PromiseObject::resolve(cx, promise, val);
}
struct InstantiateTask : CompileTask
{
PersistentRootedObject importObj;
InstantiateTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
: CompileTask(cx, promise),
importObj(cx, importObj)
{}
bool finishPromise(JSContext* cx, Handle<PromiseObject*> promise) override {
return module
? ResolveInstantiation(cx, *module, importObj, promise)
: Reject(cx, compileArgs, Move(error), promise);
}
};
static bool
GetInstantiateArgs(JSContext* cx, CallArgs callArgs, MutableHandleObject firstArg,
MutableHandleObject importObj)
{
if (!callArgs.requireAtLeast(cx, "WebAssembly.instantiate", 1))
return false;
if (!callArgs[0].isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_BUF_MOD_ARG);
return false;
}
firstArg.set(&callArgs[0].toObject());
if (!callArgs.get(1).isUndefined()) {
if (!callArgs[1].isObject())
return ThrowBadImportArg(cx);
importObj.set(&callArgs[1].toObject());
}
return true;
}
static bool
WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp)
{
if (!cx->startAsyncTaskCallback || !cx->finishAsyncTaskCallback) {
JS_ReportErrorASCII(cx, "WebAssembly.instantiate not supported in this runtime.");
return false;
}
RootedFunction nopFun(cx, NewNativeFunction(cx, Nop, 0, nullptr));
if (!nopFun)
return false;
Rooted<PromiseObject*> promise(cx, PromiseObject::create(cx, nopFun));
if (!promise)
return false;
CallArgs callArgs = CallArgsFromVp(argc, vp);
RootedObject firstArg(cx);
RootedObject importObj(cx);
if (!GetInstantiateArgs(cx, callArgs, &firstArg, &importObj))
return RejectWithPendingException(cx, promise, callArgs);
Module* module;
if (IsModuleObject(firstArg, &module)) {
RootedWasmInstanceObject instanceObj(cx);
if (!Instantiate(cx, *module, importObj, &instanceObj))
return RejectWithPendingException(cx, promise, callArgs);
RootedValue resolutionValue(cx, ObjectValue(*instanceObj));
if (!PromiseObject::resolve(cx, promise, resolutionValue))
return false;
} else {
auto task = cx->make_unique<InstantiateTask>(cx, promise, importObj);
if (!task)
return false;
if (!GetBufferSource(cx, firstArg, JSMSG_WASM_BAD_BUF_MOD_ARG, &task->bytecode))
return RejectWithPendingException(cx, promise, callArgs);
if (!InitCompileArgs(cx, &task->compileArgs))
return false;
if (!StartPromiseTask(cx, Move(task)))
return false;
}
callArgs.rval().setObject(*promise);
return true;
}
static bool
WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs callArgs = CallArgsFromVp(argc, vp);
MutableBytes bytecode;
if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode))
return false;
CompileArgs compileArgs;
if (!InitCompileArgs(cx, &compileArgs))
return false;
UniqueChars error;
bool validated = !!Compile(*bytecode, compileArgs, &error);
// If the reason for validation failure was OOM (signalled by null error
// message), report out-of-memory so that validate's return is always
// correct.
if (!validated && !error) {
ReportOutOfMemory(cx);
return false;
}
callArgs.rval().setBoolean(validated);
return true;
}
static const JSFunctionSpec WebAssembly_static_methods[] =
{
#if JS_HAS_TOSOURCE
JS_FN(js_toSource_str, WebAssembly_toSource, 0, 0),
#endif
JS_FN("compile", WebAssembly_compile, 1, 0),
JS_FN("instantiate", WebAssembly_instantiate, 2, 0),
JS_FN("validate", WebAssembly_validate, 1, 0),
JS_FS_END
};
const Class js::WebAssemblyClass =
{
js_WebAssembly_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly)
};
template <class Class>
static bool
InitConstructor(JSContext* cx, HandleObject wasm, const char* name, MutableHandleObject proto)
{
proto.set(NewBuiltinClassInstance<PlainObject>(cx, SingletonObject));
if (!proto)
return false;
if (!DefinePropertiesAndFunctions(cx, proto, Class::properties, Class::methods))
return false;
RootedAtom className(cx, Atomize(cx, name, strlen(name)));
if (!className)
return false;
RootedFunction ctor(cx, NewNativeConstructor(cx, Class::construct, 1, className));
if (!ctor)
return false;
if (!DefinePropertiesAndFunctions(cx, ctor, nullptr, Class::static_methods))
return false;
if (!LinkConstructorAndPrototype(cx, ctor, proto))
return false;
RootedId id(cx, AtomToId(className));
RootedValue ctorValue(cx, ObjectValue(*ctor));
return DefineProperty(cx, wasm, id, ctorValue, nullptr, nullptr, 0);
}
static bool
InitErrorClass(JSContext* cx, HandleObject wasm, const char* name, JSExnType exn)
{
Handle<GlobalObject*> global = cx->global();
RootedObject proto(cx, GlobalObject::getOrCreateCustomErrorPrototype(cx, global, exn));
if (!proto)
return false;
RootedAtom className(cx, Atomize(cx, name, strlen(name)));
if (!className)
return false;
RootedId id(cx, AtomToId(className));
RootedValue ctorValue(cx, global->getConstructor(GetExceptionProtoKey(exn)));
return DefineProperty(cx, wasm, id, ctorValue, nullptr, nullptr, 0);
}
JSObject*
js::InitWebAssemblyClass(JSContext* cx, HandleObject obj)
{
MOZ_RELEASE_ASSERT(HasSupport(cx));
Handle<GlobalObject*> global = obj.as<GlobalObject>();
MOZ_ASSERT(!global->isStandardClassResolved(JSProto_WebAssembly));
RootedObject proto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
if (!proto)
return nullptr;
RootedObject wasm(cx, NewObjectWithGivenProto(cx, &WebAssemblyClass, proto, SingletonObject));
if (!wasm)
return nullptr;
if (!JS_DefineFunctions(cx, wasm, WebAssembly_static_methods))
return nullptr;
RootedObject moduleProto(cx), instanceProto(cx), memoryProto(cx), tableProto(cx);
if (!InitConstructor<WasmModuleObject>(cx, wasm, "Module", &moduleProto))
return nullptr;
if (!InitConstructor<WasmInstanceObject>(cx, wasm, "Instance", &instanceProto))
return nullptr;
if (!InitConstructor<WasmMemoryObject>(cx, wasm, "Memory", &memoryProto))
return nullptr;
if (!InitConstructor<WasmTableObject>(cx, wasm, "Table", &tableProto))
return nullptr;
if (!InitErrorClass(cx, wasm, "CompileError", JSEXN_WASMCOMPILEERROR))
return nullptr;
if (!InitErrorClass(cx, wasm, "RuntimeError", JSEXN_WASMRUNTIMEERROR))
return nullptr;
// Perform the final fallible write of the WebAssembly object to a global
// object property at the end. Only after that succeeds write all the
// constructor and prototypes to the JSProto slots. This ensures that
// initialization is atomic since a failed initialization can be retried.
if (!JS_DefineProperty(cx, global, js_WebAssembly_str, wasm, JSPROP_RESOLVING))
return nullptr;
global->setPrototype(JSProto_WasmModule, ObjectValue(*moduleProto));
global->setPrototype(JSProto_WasmInstance, ObjectValue(*instanceProto));
global->setPrototype(JSProto_WasmMemory, ObjectValue(*memoryProto));
global->setPrototype(JSProto_WasmTable, ObjectValue(*tableProto));
global->setConstructor(JSProto_WebAssembly, ObjectValue(*wasm));
MOZ_ASSERT(global->isStandardClassResolved(JSProto_WebAssembly));
return wasm;
}