diff options
Diffstat (limited to 'js/src/wasm/WasmJS.cpp')
-rw-r--r-- | js/src/wasm/WasmJS.cpp | 2048 |
1 files changed, 2048 insertions, 0 deletions
diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp new file mode 100644 index 000000000..07d6331b8 --- /dev/null +++ b/js/src/wasm/WasmJS.cpp @@ -0,0 +1,2048 @@ +/* -*- 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 +WasmTableObject::getImpl(JSContext* cx, const CallArgs& args) +{ + RootedWasmTableObject tableObj(cx, &args.thisv().toObject().as<WasmTableObject>()); + const Table& table = tableObj->table(); + + uint32_t index; + if (!ToNonWrappingUint32(cx, args.get(0), table.length() - 1, "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 (!ToNonWrappingUint32(cx, args.get(0), table.length() - 1, "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 + +#ifdef SPIDERMONKEY_PROMISE +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 promise->reject(cx, 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 promise->reject(cx, 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 promise->resolve(cx, 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 promise->reject(cx, 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 promise->resolve(cx, 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 (!promise->resolve(cx, 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; +} +#endif + +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 +#ifdef SPIDERMONKEY_PROMISE + JS_FN("compile", WebAssembly_compile, 1, 0), + JS_FN("instantiate", WebAssembly_instantiate, 2, 0), +#endif + 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, global->getOrCreateObjectPrototype(cx)); + 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; +} |