Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* 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
*
*
* 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/EndianUtils.h"
#include "mozilla/Maybe.h"
#include "mozilla/RangedPtr.h"
#include <algorithm>
#include "builtin/TypedObject.h"
#include "gc/FreeOp.h"
#include "jit/AtomicOperations.h"
#include "jit/JitOptions.h"
#include "jit/Simulator.h"
#include "js/Printf.h"
#include "js/PropertySpec.h" // JS_{PS,FN}{,_END}
#if defined(JS_CODEGEN_X64) // Assembler::HasSSE41
# include "jit/x64/Assembler-x64.h"
# include "jit/x86-shared/Architecture-x86-shared.h"
# include "jit/x86-shared/Assembler-x86-shared.h"
#endif
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "vm/ErrorObject.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GlobalObject.h" // js::GlobalObject
#include "vm/Interpreter.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/PromiseObject.h" // js::PromiseObject
#include "vm/StringType.h"
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmCraneliftCompile.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmProcess.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::CheckedInt;
using mozilla::MakeSpan;
using mozilla::Nothing;
using mozilla::RangedPtr;
extern mozilla::Atomic<bool> fuzzingSafe;
static inline bool WasmMultiValueFlag(JSContext* cx) {
#ifdef ENABLE_WASM_MULTI_VALUE
return cx->options().wasmMultiValue();
#else
return false;
#endif
}
static inline bool WasmSimdFlag(JSContext* cx) {
#ifdef ENABLE_WASM_SIMD
return cx->options().wasmSimd() && js::jit::JitSupportsWasmSimd();
#else
return false;
#endif
}
/*
* [WASMDOC] Compiler and feature selection; compiler and feature availability.
*
* In order to make the computation of whether a wasm feature or wasm compiler
* is available predictable, we have established some rules, and implemented
* those rules.
*
* Code elsewhere should use the predicates below to test for features and
* compilers, it should never try to compute feature and compiler availability
* in other ways.
*
* At the outset, there is a set of selected compilers C containing at most one
* baseline compiler [*] and at most one optimizing compiler [**], and a set of
* selected features F. These selections come from defaults and from overrides
* by command line switches in the shell and javascript.option.wasm_X in the
* browser. Defaults for both features and compilers may be platform specific,
* for example, some compilers may not be available on some platforms because
* they do not support the architecture at all or they do not support features
* that must be enabled by default on the platform.
*
* [*] Currently we have only one, "baseline" aka "Rabaldr", but other
* implementations have additional baseline translators, eg from wasm
* bytecode to an internal code processed by an interpreter.
*
* [**] Currently we have two, "ion" aka "Baldr", and "Cranelift".
*
*
* Compiler availability:
*
* The set of features F induces a set of available compilers A: these are the
* compilers that all support all the features in F. (Some of these compilers
* may not be in the set C.)
*
* The sets C and A are intersected, yielding a set of enabled compilers E.
* Notably, the set E may be empty, in which case wasm is effectively disabled
* (though the WebAssembly object is still present in the global environment).
*
* An important consequence is that selecting a feature that is not supported by
* a particular compiler disables that compiler completely -- there is no notion
* of a compiler being available but suddenly failing when an unsupported
* feature is used by a program. If a compiler is available, it supports all
* the features that have been selected.
*
* Equally important, a feature cannot be enabled by default on a platform if
* the feature is not supported by all the compilers we wish to have enabled by
* default on the platform. We MUST by-default disable features on a platform
* that are not supported by all the compilers on the platform.
*
* As an example:
*
* On ARM64 the default compilers are Baseline and Cranelift. Say Cranelift
* does not support feature X. Thus X cannot be enabled by default on ARM64.
* However, X support can be compiled-in to SpiderMonkey, and the user can opt
* to enable X. Doing so will disable Cranelift.
*
* In contrast, X can be enabled by default on x64, where the default
* compilers are Baseline and Ion, both of which support X.
*
* A subtlety is worth noting: on x64, enabling Cranelift (thus disabling Ion)
* will not disable X. Instead, the presence of X in the selected feature set
* will disable Cranelift, leaving only Baseline. This follows from the logic
* described above.
*
* In a shell build, the testing functions wasmCompilersPresent,
* wasmCompileMode, wasmCraneliftDisabledByFeatures, and
* wasmIonDisabledByFeatures can be used to probe compiler availability and the
* reasons for a compiler being unavailable.
*
*
* Feature availability:
*
* A feature is available if it is selected and there is at least one available
* compiler that implements it.
*
* For example, --wasm-gc selects the GC feature, and if Baseline is available
* then the feature is available.
*
* In a shell build, there are per-feature testing functions (usually of the
* form wasmFeatureEnabled, wasmFeatureSupport, or wasmFeatureSupported) to
* probe whether specific features are available.
*/
// Compiler availability predicates. These must be kept in sync with the
// feature predicates in the next section below.
//
// These can't call the feature predicates since the feature predicates call
// back to these predicates. So there will be a small amount of duplicated
// logic here, but as compilers reach feature parity that duplication will go
// away.
//
// There's a static precedence order between the optimizing compilers. This
// order currently ranks Cranelift over Ion on all platforms because Cranelift
// is disabled by default on all platforms: anyone who has enabled Cranelift
// will wish to use it instead of Ion.
//
// The precedence order is implemented by guards in IonAvailable() and
// CraneliftAvailable(). We expect that it will become more complex as the
// default settings change. But it should remain static.
bool wasm::BaselineAvailable(JSContext* cx) {
// Baseline supports every feature supported by any compiler.
return cx->options().wasmBaseline() && BaselinePlatformSupport();
}
bool wasm::IonAvailable(JSContext* cx) {
if (!cx->options().wasmIon() || !IonPlatformSupport()) {
return false;
}
bool isDisabled = false;
MOZ_ALWAYS_TRUE(IonDisabledByFeatures(cx, &isDisabled));
return !isDisabled && !CraneliftAvailable(cx);
}
template <size_t ArrayLength>
static inline bool Append(JSStringBuilder* reason, const char (&s)[ArrayLength],
char* sep) {
if ((*sep && !reason->append(*sep)) || !reason->append(s)) {
return false;
}
*sep = ',';
return true;
}
bool wasm::IonDisabledByFeatures(JSContext* cx, bool* isDisabled,
JSStringBuilder* reason) {
// Ion has no debugging support, no gc support.
bool debug = cx->realm() && cx->realm()->debuggerObservesAsmJS();
bool gc = cx->options().wasmGc();
if (reason) {
char sep = 0;
if (debug && !Append(reason, "debug", &sep)) {
return false;
}
if (gc && !Append(reason, "gc", &sep)) {
return false;
}
}
*isDisabled = debug || gc;
return true;
}
bool wasm::CraneliftAvailable(JSContext* cx) {
if (!cx->options().wasmCranelift() || !CraneliftPlatformSupport()) {
return false;
}
bool isDisabled = false;
MOZ_ALWAYS_TRUE(CraneliftDisabledByFeatures(cx, &isDisabled));
return !isDisabled;
}
bool wasm::CraneliftDisabledByFeatures(JSContext* cx, bool* isDisabled,
JSStringBuilder* reason) {
// Cranelift has no debugging support, no gc support, no multi-value support,
// no threads, no simd, and on ARM64, no reference types.
bool debug = cx->realm() && cx->realm()->debuggerObservesAsmJS();
bool gc = cx->options().wasmGc();
bool threads =
cx->realm() &&
cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled();
#if defined(JS_CODEGEN_ARM64)
bool reftypesOnArm64 = cx->options().wasmReftypes();
bool multiValue = false;
#else
// On other platforms, assume reftypes has been implemented.
bool reftypesOnArm64 = false;
bool multiValue = WasmMultiValueFlag(cx);
#endif
bool simd = WasmSimdFlag(cx);
if (reason) {
char sep = 0;
if (debug && !Append(reason, "debug", &sep)) {
return false;
}
if (gc && !Append(reason, "gc", &sep)) {
return false;
}
if (multiValue && !Append(reason, "multi-value", &sep)) {
return false;
}
if (threads && !Append(reason, "threads", &sep)) {
return false;
}
if (reftypesOnArm64 && !Append(reason, "reftypes", &sep)) {
return false;
}
if (simd && !Append(reason, "simd", &sep)) {
return false;
}
}
*isDisabled = debug || gc || multiValue || threads || reftypesOnArm64 || simd;
return true;
}
// Feature predicates. These must be kept in sync with the predicates in the
// section above.
//
// The meaning of these predicates is tricky: A predicate is true for a feature
// if the feature is enabled and/or compiled-in *and* we have *at least one*
// compiler that can support the feature. Subsequent compiler selection must
// ensure that only compilers that actually support the feature are used.
bool wasm::ReftypesAvailable(JSContext* cx) {
// All compilers support reference types, except Cranelift on ARM64.
#ifdef ENABLE_WASM_REFTYPES
return cx->options().wasmReftypes() &&
(BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx));
#else
return false;
#endif
}
bool wasm::GcTypesAvailable(JSContext* cx) {
// Cranelift and Ion do not support GC.
return cx->options().wasmGc() && BaselineAvailable(cx);
}
bool wasm::MultiValuesAvailable(JSContext* cx) {
return WasmMultiValueFlag(cx) &&
(BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx));
}
bool wasm::SimdAvailable(JSContext* cx) {
// Cranelift does not support SIMD.
return WasmSimdFlag(cx) && (BaselineAvailable(cx) || IonAvailable(cx));
}
bool wasm::ThreadsAvailable(JSContext* cx) {
// Cranelift does not support atomics.
return cx->realm() &&
cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled() &&
(BaselineAvailable(cx) || IonAvailable(cx));
}
bool wasm::HasPlatformSupport(JSContext* cx) {
#if !MOZ_LITTLE_ENDIAN() || defined(JS_CODEGEN_NONE)
return false;
#endif
if (gc::SystemPageSize() > wasm::PageSize) {
return false;
}
if (!JitOptions.supportsFloatingPoint) {
return false;
}
if (!JitOptions.supportsUnalignedAccesses) {
return false;
}
if (!wasm::EnsureFullSignalHandlers(cx)) {
return false;
}
// Wasm threads require 8-byte lock-free atomics.
if (!jit::AtomicOperations::isLockfree8()) {
return false;
}
#ifdef JS_SIMULATOR
if (!Simulator::supportsAtomics()) {
return false;
}
#endif
// Test only whether the compilers are supported on the hardware, not whether
// they are enabled.
return BaselinePlatformSupport() || IonPlatformSupport() ||
CraneliftPlatformSupport();
}
bool wasm::HasSupport(JSContext* cx) {
// If the general wasm pref is on, it's on for everything.
bool prefEnabled = cx->options().wasm();
// If the general pref is off, check trusted principals.
if (MOZ_UNLIKELY(!prefEnabled)) {
prefEnabled = cx->options().wasmForTrustedPrinciples() && cx->realm() &&
cx->realm()->principals() &&
cx->realm()->principals()->isSystemOrAddonPrincipal();
}
return prefEnabled && HasPlatformSupport(cx) &&
(BaselineAvailable(cx) || IonAvailable(cx) || CraneliftAvailable(cx));
}
bool wasm::StreamingCompilationAvailable(JSContext* cx) {
// This should match EnsureStreamSupport().
return HasSupport(cx) &&
cx->runtime()->offThreadPromiseState.ref().initialized() &&
CanUseExtraThreads() && cx->runtime()->consumeStreamCallback &&
cx->runtime()->reportStreamErrorCallback;
}
bool wasm::CodeCachingAvailable(JSContext* cx) {
// At the moment, we require Ion support for code caching. The main reason
// for this is that wasm::CompileAndSerialize() does not have access to
// information about which optimizing compiler it should use. See comments in
// CompileAndSerialize(), below.
return StreamingCompilationAvailable(cx) && IonAvailable(cx);
}
bool wasm::CheckRefType(JSContext* cx, RefType::Kind targetTypeKind,
HandleValue v, MutableHandleFunction fnval,
MutableHandleAnyRef refval) {
switch (targetTypeKind) {
case RefType::Func:
if (!CheckFuncRefValue(cx, v, fnval)) {
return false;
}
break;
case RefType::Any:
if (!BoxAnyRef(cx, v, refval)) {
return false;
}
break;
case RefType::TypeIndex:
MOZ_CRASH("temporarily unsupported Ref type");
}
return true;
}
static bool ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v,
MutableHandleVal val) {
switch (targetType.kind()) {
case ValType::I32: {
int32_t i32;
if (!ToInt32(cx, v, &i32)) {
return false;
}
val.set(Val(uint32_t(i32)));
return true;
}
case ValType::F32: {
double d;
if (!ToNumber(cx, v, &d)) {
return false;
}
val.set(Val(float(d)));
return true;
}
case ValType::F64: {
double d;
if (!ToNumber(cx, v, &d)) {
return false;
}
val.set(Val(d));
return true;
}
case ValType::I64: {
BigInt* bigint = ToBigInt(cx, v);
if (!bigint) {
return false;
}
val.set(Val(BigInt::toUint64(bigint)));
return true;
}
case ValType::Ref: {
RootedFunction fun(cx);
RootedAnyRef any(cx, AnyRef::null());
if (!CheckRefType(cx, targetType.refTypeKind(), v, &fun, &any)) {
return false;
}
switch (targetType.refTypeKind()) {
case RefType::Func:
val.set(Val(RefType::func(), FuncRef::fromJSFunction(fun)));
return true;
case RefType::Any:
val.set(Val(targetType.refType(), any));
return true;
case RefType::TypeIndex:
break;
}
break;
}
case ValType::V128: {
break;
}
}
MOZ_CRASH("unexpected import value type, caller must guard");
}
static bool ToJSValue(JSContext* cx, const Val& val, MutableHandleValue out) {
switch (val.type().kind()) {
case ValType::I32:
out.setInt32(val.i32());
return true;
case ValType::F32:
out.setDouble(JS::CanonicalizeNaN(double(val.f32())));
return true;
case ValType::F64:
out.setDouble(JS::CanonicalizeNaN(val.f64()));
return true;
case ValType::I64: {
BigInt* bi = BigInt::createFromInt64(cx, val.i64());
if (!bi) {
return false;
}
out.setBigInt(bi);
return true;
}
case ValType::Ref:
switch (val.type().refTypeKind()) {
case RefType::Func:
out.set(UnboxFuncRef(FuncRef::fromAnyRefUnchecked(val.ref())));
return true;
case RefType::Any:
out.set(UnboxAnyRef(val.ref()));
return true;
case RefType::TypeIndex:
break;
}
break;
case ValType::V128:
break;
}
MOZ_CRASH("unexpected type when translating to a JS value");
}
// ============================================================================
// Imports
static bool ThrowBadImportArg(JSContext* cx) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_ARG);
return false;
}
static bool ThrowBadImportType(JSContext* cx, const char* field,
const char* str) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_TYPE, 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);
}
bool js::wasm::GetImports(JSContext* cx, const Module& module,
HandleObject importObj, ImportValues* imports) {
if (!module.imports().empty() && !importObj) {
return ThrowBadImportArg(cx);
}
const Metadata& metadata = module.metadata();
uint32_t globalIndex = 0;
const GlobalDescVector& globals = metadata.globals;
uint32_t tableIndex = 0;
const TableDescVector& tables = metadata.tables;
for (const Import& import : module.imports()) {
RootedValue v(cx);
if (!GetProperty(cx, importObj, import.module.get(), &v)) {
return false;
}
if (!v.isObject()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMPORT_FIELD,
import.module.get());
return false;
}
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 ThrowBadImportType(cx, import.field.get(), "Function");
}
if (!imports->funcs.append(&v.toObject().as<JSFunction>())) {
return false;
}
break;
}
case DefinitionKind::Table: {
const uint32_t index = tableIndex++;
if (!v.isObject() || !v.toObject().is<WasmTableObject>()) {
return ThrowBadImportType(cx, import.field.get(), "Table");
}
RootedWasmTableObject obj(cx, &v.toObject().as<WasmTableObject>());
if (obj->table().kind() != tables[index].kind) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_TBL_TYPE_LINK);
return false;
}
if (!imports->tables.append(obj)) {
return false;
}
break;
}
case DefinitionKind::Memory: {
if (!v.isObject() || !v.toObject().is<WasmMemoryObject>()) {
return ThrowBadImportType(cx, import.field.get(), "Memory");
}
MOZ_ASSERT(!imports->memory);
imports->memory = &v.toObject().as<WasmMemoryObject>();
break;
}
case DefinitionKind::Global: {
const uint32_t index = globalIndex++;
const GlobalDesc& global = globals[index];
MOZ_ASSERT(global.importIndex() == index);
RootedVal val(cx);
if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
if (obj->isMutable() != global.isMutable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_MUT_LINK);
return false;
}
if (obj->type() != global.type()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_TYPE_LINK);
return false;
}
if (imports->globalObjs.length() <= index &&
!imports->globalObjs.resize(index + 1)) {
ReportOutOfMemory(cx);
return false;
}
imports->globalObjs[index] = obj;
obj->val(&val);
} else {
if (IsNumberType(global.type())) {
if (global.type() == ValType::I64 && !v.isBigInt()) {
return ThrowBadImportType(cx, import.field.get(), "BigInt");
}
if (global.type() != ValType::I64 && !v.isNumber()) {
return ThrowBadImportType(cx, import.field.get(), "Number");
}
} else {
MOZ_ASSERT(global.type().isReference());
if (!global.type().isAnyRef() && !v.isObjectOrNull()) {
return ThrowBadImportType(cx, import.field.get(),
"Object-or-null value required for "
"non-externref reference type");
}
}
if (global.isMutable()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_GLOB_MUT_LINK);
return false;
}
if (global.type() == ValType::V128) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_VAL_TYPE);
return false;
}
if (!ToWebAssemblyValue(cx, global.type(), v, &val)) {
return false;
}
}
if (!imports->globalValues.append(val)) {
return false;
}
break;
}
}
}
MOZ_ASSERT(globalIndex == globals.length() ||
!globals[globalIndex].isImport());
return true;
}
static bool DescribeScriptedCaller(JSContext* cx, ScriptedCaller* caller,
const char* introducer) {
// 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, &caller->line)) {
caller->filename =
FormatIntroducedFilename(cx, af.get(), caller->line, introducer);
if (!caller->filename) {
return false;
}
}
return true;
}
// ============================================================================
// Testing / Fuzzing support
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->dataPointerEither().unwrap(),
code->byteLength())) {
ReportOutOfMemory(cx);
return false;
}
ScriptedCaller scriptedCaller;
if (!DescribeScriptedCaller(cx, &scriptedCaller, "wasm_eval")) {
return false;
}
SharedCompileArgs compileArgs =
CompileArgs::build(cx, std::move(scriptedCaller));
if (!compileArgs) {
return false;
}
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
if (!module) {
if (error) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_COMPILE_ERROR, error.get());
return false;
}
ReportOutOfMemory(cx);
return false;
}
Rooted<ImportValues> imports(cx);
if (!GetImports(cx, *module, importObj, imports.address())) {
return false;
}
return module->instantiate(cx, imports.get(), nullptr, instanceObj);
}
struct MOZ_STACK_CLASS SerializeListener : JS::OptimizedEncodingListener {
// MOZ_STACK_CLASS means these can be nops.
MozExternalRefCountType MOZ_XPCOM_ABI AddRef() override { return 0; }
MozExternalRefCountType MOZ_XPCOM_ABI Release() override { return 0; }
DebugOnly<bool> called = false;
Bytes* serialized;
explicit SerializeListener(Bytes* serialized) : serialized(serialized) {}
void storeOptimizedEncoding(JS::UniqueOptimizedEncodingBytes bytes) override {
MOZ_ASSERT(!called);
called = true;
if (serialized->resize(bytes->length())) {
memcpy(serialized->begin(), bytes->begin(), bytes->length());
}
}
};
bool wasm::CompileAndSerialize(const ShareableBytes& bytecode,
Bytes* serialized) {
MutableCompileArgs compileArgs = js_new<CompileArgs>(ScriptedCaller());
if (!compileArgs) {
return false;
}
// The caller has ensured CodeCachingAvailable(). Moreover, we want to ensure
// we go straight to tier-2 so that we synchronously call
// JS::OptimizedEncodingListener::storeOptimizedEncoding().
compileArgs->baselineEnabled = false;
// We always pick Ion here, and we depend on CodeCachingAvailable() having
// determined that Ion is available, see comments at CodeCachingAvailable().
// To do better, we need to pass information about which compiler that should
// be used into CompileAndSerialize().
compileArgs->ionEnabled = true;
// The caller must ensure that huge memory support is configured the same in
// the receiving process of this serialized module.
compileArgs->hugeMemory = wasm::IsHugeMemoryEnabled();
SerializeListener listener(serialized);
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, bytecode, &error, &warnings, &listener);
if (!module) {
fprintf(stderr, "Compilation error: %s\n", error ? error.get() : "oom");
return false;
}
MOZ_ASSERT(module->code().hasTier(Tier::Serialized));
MOZ_ASSERT(listener.called);
return !listener.serialized->empty();
}
bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized,
MutableHandleObject moduleObj) {
MutableModule module =
Module::deserialize(serialized.begin(), serialized.length());
if (!module) {
ReportOutOfMemory(cx);
return false;
}
moduleObj.set(module->createObject(cx));
return !!moduleObj;
}
// ============================================================================
// Common functions
// '[EnforceRange] unsigned long' types are coerced with
// ConvertToInt(v, 32, 'unsigned')
// defined in Web IDL Section 3.2.4.9.
static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
const char* noun, uint32_t* u32) {
// Step 4.
double x;
if (!ToNumber(cx, v, &x)) {
return false;
}
// Step 5.
if (mozilla::IsNegativeZero(x)) {
x = 0.0;
}
// Step 6.1.
if (!mozilla::IsFinite(x)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_UINT32, kind, noun);
return false;
}
// Step 6.2.
x = JS::ToInteger(x);
// Step 6.3.
if (x < 0 || x > double(UINT32_MAX)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_UINT32, kind, noun);
return false;
}
*u32 = uint32_t(x);
MOZ_ASSERT(double(*u32) == x);
return true;
}
static bool GetLimits(JSContext* cx, HandleObject obj, uint32_t maximumField,
const char* kind, Limits* limits, Shareable allowShared) {
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;
}
uint32_t initial = 0;
if (!EnforceRangeU32(cx, initialVal, kind, "initial size", &initial)) {
return false;
}
limits->initial = initial;
if (limits->initial > maximumField) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_RANGE,
kind, "initial size");
return false;
}
JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
if (!maximumAtom) {
return false;
}
RootedId maximumId(cx, AtomToId(maximumAtom));
RootedValue maxVal(cx);
if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) {
return false;
}
// maxVal does not have a default value.
if (!maxVal.isUndefined()) {
uint32_t maximum = 0;
if (!EnforceRangeU32(cx, maxVal, kind, "maximum size", &maximum)) {
return false;
}
limits->maximum = Some(maximum);
if (*limits->maximum > maximumField || limits->initial > *limits->maximum) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_RANGE, kind, "maximum size");
return false;
}
}
limits->shared = Shareable::False;
if (allowShared == Shareable::True) {
JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
if (!sharedAtom) {
return false;
}
RootedId sharedId(cx, AtomToId(sharedAtom));
RootedValue sharedVal(cx);
if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
return false;
}
// shared's default value is false, which is already the value set above.
if (!sharedVal.isUndefined()) {
limits->shared =
ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
if (limits->shared == Shareable::True) {
if (maxVal.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_MAXIMUM, kind);
return false;
}
if (!cx->realm()
->creationOptions()
.getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK);
return false;
}
}
}
}
return true;
}
template <class Class, const char* name>
static JSObject* CreateWasmConstructor(JSContext* cx, JSProtoKey key) {
RootedAtom className(cx, Atomize(cx, name, strlen(name)));
if (!className) {
return nullptr;
}
return NewNativeConstructor(cx, Class::construct, 1, className);
}
// ============================================================================
// WebAssembly.Module class and methods
const JSClassOps WasmModuleObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
WasmModuleObject::finalize, // finalize
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
nullptr, // trace
};
const JSClass WasmModuleObject::class_ = {
"WebAssembly.Module",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmModuleObject::classOps_,
&WasmModuleObject::classSpec_,
};
const JSClass& WasmModuleObject::protoClass_ = PlainObject::class_;
static constexpr char WasmModuleName[] = "Module";
const ClassSpec WasmModuleObject::classSpec_ = {
CreateWasmConstructor<WasmModuleObject, WasmModuleName>,
GenericCreatePrototype<WasmModuleObject>,
WasmModuleObject::static_methods,
nullptr,
WasmModuleObject::methods,
WasmModuleObject::properties,
nullptr,
ClassSpec::DontDefineConstructor};
const JSPropertySpec WasmModuleObject::properties[] = {
JS_STRING_SYM_PS(toStringTag, "WebAssembly.Module", JSPROP_READONLY),
JS_PS_END};
const JSFunctionSpec WasmModuleObject::methods[] = {JS_FS_END};
const JSFunctionSpec WasmModuleObject::static_methods[] = {
JS_FN("imports", WasmModuleObject::imports, 1, JSPROP_ENUMERATE),
JS_FN("exports", WasmModuleObject::exports, 1, JSPROP_ENUMERATE),
JS_FN("customSections", WasmModuleObject::customSections, 2,
JSPROP_ENUMERATE),
JS_FS_END};
/* static */
void WasmModuleObject::finalize(JSFreeOp* fop, JSObject* obj) {
const Module& module = obj->as<WasmModuleObject>().module();
obj->zone()->decJitMemory(module.codeLength(module.code().stableTier()));
fop->release(obj, &module, module.gcMallocBytesExcludingCode(),
MemoryUse::WasmModule);
}
static bool IsModuleObject(JSObject* obj, const Module** module) {
WasmModuleObject* mobj = obj->maybeUnwrapIf<WasmModuleObject>();
if (!mobj) {
return false;
}
*module = &mobj->module();
return true;
}
static bool GetModuleArg(JSContext* cx, CallArgs args, uint32_t numRequired,
const char* name, const Module** module) {
if (!args.requireAtLeast(cx, name, numRequired)) {
return false;
}
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), module)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_MOD_ARG);
return false;
}
return true;
}
struct KindNames {
RootedPropertyName kind;
RootedPropertyName table;
RootedPropertyName memory;
RootedPropertyName signature;
explicit KindNames(JSContext* cx)
: kind(cx), table(cx), memory(cx), signature(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();
JSAtom* signature = Atomize(cx, "signature", strlen("signature"));
if (!signature) {
return false;
}
names->signature = signature->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* FuncTypeToString(JSContext* cx, const FuncType& funcType) {
JSStringBuilder buf(cx);
if (!buf.append('(')) {
return nullptr;
}
bool first = true;
for (ValType arg : funcType.args()) {
if (!first && !buf.append(", ", strlen(", "))) {
return nullptr;
}
const char* argStr = ToCString(arg);
if (!buf.append(argStr, strlen(argStr))) {
return nullptr;
}
first = false;
}
if (!buf.append(") -> (", strlen(") -> ("))) {
return nullptr;
}
first = true;
for (ValType result : funcType.results()) {
if (!first && !buf.append(", ", strlen(", "))) {
return nullptr;
}
const char* resultStr = ToCString(result);
if (!buf.append(resultStr, strlen(resultStr))) {
return nullptr;
}
first = false;
}
if (!buf.append(')')) {
return nullptr;
}
return buf.finishString();
}
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);
const Module* module;
if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.imports", &module)) {
return false;
}
KindNames names(cx);
if (!InitKindNames(cx, &names)) {
return false;
}
RootedValueVector elems(cx);
if (!elems.reserve(module->imports().length())) {
return false;
}
const FuncImportVector& funcImports =
module->metadata(module->code().stableTier()).funcImports;
size_t numFuncImport = 0;
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)));
if (fuzzingSafe && import.kind == DefinitionKind::Function) {
JSString* ftStr =
FuncTypeToString(cx, funcImports[numFuncImport++].funcType());
if (!ftStr) {
return false;
}
if (!props.append(
IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
return false;
}
}
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);
const Module* module;
if (!GetModuleArg(cx, args, 1, "WebAssembly.Module.exports", &module)) {
return false;
}
KindNames names(cx);
if (!InitKindNames(cx, &names)) {
return false;
}
RootedValueVector 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)));
if (fuzzingSafe && exp.kind() == DefinitionKind::Function) {
const FuncExport& fe = module->metadata(module->code().stableTier())
.lookupFuncExport(exp.funcIndex());
JSString* ftStr = FuncTypeToString(cx, fe.funcType());
if (!ftStr) {
return false;
}
if (!props.append(
IdValuePair(NameToId(names.signature), StringValue(ftStr)))) {
return false;
}
}
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::customSections(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
const Module* module;
if (!GetModuleArg(cx, args, 2, "WebAssembly.Module.customSections",
&module)) {
return false;
}
Vector<char, 8> name(cx);
{
RootedString str(cx, ToString(cx, args.get(1)));
if (!str) {
return false;
}
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}
if (!name.initLengthUninitialized(
JS::GetDeflatedUTF8StringLength(linear))) {
return false;
}
mozilla::Unused << JS::DeflateStringToUTF8Buffer(
linear, MakeSpan(name.begin(), name.length()));
}
RootedValueVector elems(cx);
RootedArrayBufferObject buf(cx);
for (const CustomSection& cs : module->customSections()) {
if (name.length() != cs.name.length()) {
continue;
}
if (memcmp(name.begin(), cs.name.begin(), name.length())) {
continue;
}
buf = ArrayBufferObject::createZeroed(cx, cs.payload->length());
if (!buf) {
return false;
}
memcpy(buf->dataPointer(), cs.payload->begin(), cs.payload->length());
if (!elems.append(ObjectValue(*buf))) {
return false;
}
}
JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin());
if (!arr) {
return false;
}
args.rval().setObject(*arr);
return true;
}
/* static */
WasmModuleObject* WasmModuleObject::create(JSContext* cx, const Module& module,
HandleObject proto) {
AutoSetNewObjectMetadata metadata(cx);
auto* obj = NewObjectWithGivenProto<WasmModuleObject>(cx, proto);
if (!obj) {
return nullptr;
}
// This accounts for module allocation size (excluding code which is handled
// separately - see below). This assumes that the size of associated data
// doesn't change for the life of the WasmModuleObject. The size is counted
// once per WasmModuleObject referencing a Module.
InitReservedSlot(obj, MODULE_SLOT, const_cast<Module*>(&module),
module.gcMallocBytesExcludingCode(), MemoryUse::WasmModule);
module.AddRef();
// Bug 1569888: We account for the first tier here; the second tier, if
// different, also needs to be accounted for.
cx->zone()->incJitMemory(module.codeLength(module.code().stableTier()));
return obj;
}
static bool GetBufferSource(JSContext* cx, JSObject* obj, unsigned errorNumber,
MutableBytes* bytecode) {
*bytecode = cx->new_<ShareableBytes>();
if (!*bytecode) {
return false;
}
JSObject* unwrapped = CheckedUnwrapStatic(obj);
SharedMem<uint8_t*> dataPointer;
size_t byteLength;
if (!unwrapped || !IsBufferSource(unwrapped, &dataPointer, &byteLength)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, errorNumber);
return false;
}
if (!(*bytecode)->append(dataPointer.unwrap(), byteLength)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
static SharedCompileArgs InitCompileArgs(JSContext* cx,
const char* introducer) {
ScriptedCaller scriptedCaller;
if (!DescribeScriptedCaller(cx, &scriptedCaller, introducer)) {
return nullptr;
}
return CompileArgs::build(cx, std::move(scriptedCaller));
}
static bool ReportCompileWarnings(JSContext* cx,
const UniqueCharsVector& warnings) {
// Avoid spamming the console.
size_t numWarnings = std::min<size_t>(warnings.length(), 3);
for (size_t i = 0; i < numWarnings; i++) {
if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING, warnings[i].get())) {
return false;
}
}
if (warnings.length() > numWarnings) {
if (!WarnNumberASCII(cx, JSMSG_WASM_COMPILE_WARNING,
"other warnings suppressed")) {
return false;
}
}
return true;
}
/* static */
bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) {
CallArgs callArgs = CallArgsFromVp(argc, vp);
Log(cx, "sync new Module() started");
if (!ThrowIfNotConstructing(cx, callArgs, "Module")) {
return false;
}
if (!callArgs.requireAtLeast(cx, "WebAssembly.Module", 1)) {
return false;
}
if (!callArgs[0].isObject()) {
JS_ReportErrorNumberUTF8(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;
}
SharedCompileArgs compileArgs = InitCompileArgs(cx, "WebAssembly.Module");
if (!compileArgs) {
return false;
}
UniqueChars error;
UniqueCharsVector warnings;
SharedModule module =
CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
if (!module) {
if (error) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_COMPILE_ERROR, error.get());
return false;
}
ReportOutOfMemory(cx);
return false;
}
if (!ReportCompileWarnings(cx, warnings)) {
return false;
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, callArgs, JSProto_WasmModule,
&proto)) {
return false;
}
if (!proto) {
proto = GlobalObject::getOrCreatePrototype(cx, JSProto_WasmModule);
}
RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
if (!moduleObj) {
return false;
}
Log(cx, "sync new Module() succeded");
callArgs.rval().setObject(*moduleObj);
return true;
}
const Module& WasmModuleObject::module() const {
MOZ_ASSERT(is<WasmModuleObject>());
return *(const Module*)getReservedSlot(MODULE_SLOT).toPrivate();
}
// ============================================================================
// WebAssembly.Instance class and methods
const JSClassOps WasmInstanceObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
WasmInstanceObject::finalize, // finalize
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
WasmInstanceObject::trace, // trace
};
const JSClass WasmInstanceObject::class_ = {
"WebAssembly.Instance",
JSCLASS_DELAY_METADATA_BUILDER |
JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceObject::RESERVED_SLOTS) |
JSCLASS_FOREGROUND_FINALIZE,
&WasmInstanceObject::classOps_,
&WasmInstanceObject::classSpec_,
};
const JSClass& WasmInstanceObject::protoClass_ = PlainObject::class_;
static constexpr char WasmInstanceName[] = "Instance";
const ClassSpec WasmInstanceObject::classSpec_ = {
CreateWasmConstructor<WasmInstanceObject, WasmInstanceName>,
GenericCreatePrototype<WasmInstanceObject>,
WasmInstanceObject::static_methods,
nullptr,
WasmInstanceObject::methods,
WasmInstanceObject::properties,
nullptr,
ClassSpec::DontDefineConstructor};
static bool IsInstance(HandleValue v) {
return v.isObject() && v.toObject().is<WasmInstanceObject>();
}
/* static */
bool WasmInstanceObject::exportsGetterImpl(JSContext* cx,
const CallArgs& args) {
args.rval().setObject(
args.thisv().toObject().as<WasmInstanceObject>().exportsObj());
return true;
}
/* static */
bool WasmInstanceObject::exportsGetter(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsInstance, exportsGetterImpl>(cx, args);
}
const JSPropertySpec WasmInstanceObject::properties[] = {
JS_PSG("exports", WasmInstanceObject::exportsGetter, JSPROP_ENUMERATE),
JS_STRING_SYM_PS(toStringTag, "WebAssembly.Instance", JSPROP_READONLY),
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(JSFreeOp* fop, JSObject* obj) {
WasmInstanceObject& instance = obj->as<WasmInstanceObject>();
fop->delete_(obj, &instance.exports(), MemoryUse::WasmInstanceExports);
fop->delete_(obj, &instance.scopes(), MemoryUse::WasmInstanceScopes);
fop->delete_(obj, &instance.indirectGlobals(),
MemoryUse::WasmInstanceGlobals);
if (!instance.isNewborn()) {
if (instance.instance().debugEnabled()) {
instance.instance().debug().finalize(fop);
}
fop->delete_(obj, &instance.instance(), MemoryUse::WasmInstanceInstance);
}
}
/* static */
void WasmInstanceObject::trace(JSTracer* trc, JSObject* obj) {
WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
instanceObj.exports().trace(trc);
instanceObj.indirectGlobals().trace(trc);
if (!instanceObj.isNewborn()) {
instanceObj.instance().tracePrivate(trc);
}
}
/* static */
WasmInstanceObject* WasmInstanceObject::create(
JSContext* cx, SharedCode code, const DataSegmentVector& dataSegments,
const ElemSegmentVector& elemSegments, UniqueTlsData tlsData,
HandleWasmMemoryObject memory, SharedTableVector&& tables,
StructTypeDescrVector&& structTypeDescrs,
const JSFunctionVector& funcImports, const GlobalDescVector& globals,
const ValVector& globalImportValues,
const WasmGlobalObjectVector& globalObjs, HandleObject proto,
UniqueDebugState maybeDebug) {
UniquePtr<ExportMap> exports = js::MakeUnique<ExportMap>(cx->zone());
if (!exports) {
ReportOutOfMemory(cx);
return nullptr;
}
UniquePtr<ScopeMap> scopes = js::MakeUnique<ScopeMap>(cx->zone(), cx->zone());
if (!scopes) {
ReportOutOfMemory(cx);
return nullptr;
}
uint32_t indirectGlobals = 0;
for (uint32_t i = 0; i < globalObjs.length(); i++) {
if (globalObjs[i] && globals[i].isIndirect()) {
indirectGlobals++;
}
}
Rooted<UniquePtr<GlobalObjectVector>> indirectGlobalObjs(
cx, js::MakeUnique<GlobalObjectVector>(cx->zone()));
if (!indirectGlobalObjs || !indirectGlobalObjs->resize(indirectGlobals)) {
ReportOutOfMemory(cx);
return nullptr;
}
{
uint32_t next = 0;
for (uint32_t i = 0; i < globalObjs.length(); i++) {
if (globalObjs[i] && globals[i].isIndirect()) {
(*indirectGlobalObjs)[next++] = globalObjs[i];
}
}
}
Instance* instance = nullptr;
RootedWasmInstanceObject obj(cx);
{
// We must delay creating metadata for this object until after all its
// slots have been initialized. We must also create the metadata before
// calling Instance::init as that may allocate new objects.
AutoSetNewObjectMetadata metadata(cx);
obj = NewObjectWithGivenProto<WasmInstanceObject>(cx, proto);
if (!obj) {
return nullptr;
}
MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
// Finalization assumes these slots are always initialized:
InitReservedSlot(obj, EXPORTS_SLOT, exports.release(),
MemoryUse::WasmInstanceExports);
InitReservedSlot(obj, SCOPES_SLOT, scopes.release(),
MemoryUse::WasmInstanceScopes);
InitReservedSlot(obj, GLOBALS_SLOT, indirectGlobalObjs.release(),
MemoryUse::WasmInstanceGlobals);
obj->initReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
// The INSTANCE_SLOT may not be initialized if Instance allocation fails,
// leading to an observable "newborn" state in tracing/finalization.
MOZ_ASSERT(obj->isNewborn());
// Root the Instance via WasmInstanceObject before any possible GC.
instance = cx->new_<Instance>(
cx, obj, code, std::move(tlsData), memory, std::move(tables),
std::move(structTypeDescrs), std::move(maybeDebug));
if (!instance) {
return nullptr;
}
InitReservedSlot(obj, INSTANCE_SLOT, instance,
MemoryUse::WasmInstanceInstance);
MOZ_ASSERT(!obj->isNewborn());
}
if (!instance->init(cx, funcImports, globalImportValues, globalObjs,
dataSegments, elemSegments)) {
return nullptr;
}
return obj;
}
void WasmInstanceObject::initExportsObj(JSObject& exportsObj) {
MOZ_ASSERT(getReservedSlot(EXPORTS_OBJ_SLOT).isUndefined());
setReservedSlot(EXPORTS_OBJ_SLOT, ObjectValue(exportsObj));
}
static bool GetImportArg(JSContext* cx, CallArgs callArgs,
MutableHandleObject importObj) {
if (!callArgs.get(1).isUndefined()) {
if (!callArgs[1].isObject()) {
return ThrowBadImportArg(cx);
}
importObj.set(&callArgs[1].toObject());
}
return true;
}
/* static */
bool WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Log(cx, "sync new Instance() started");
if (!ThrowIfNotConstructing(cx, args, "Instance")) {
return false;
}
if (!args.requireAtLeast(cx, "WebAssembly.Instance", 1)) {
return false;
}
const Module* module;
if (!args[0].isObject() || !IsModuleObject(&args[0].toObject(), &module)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_MOD_ARG);
return false;
}
RootedObject importObj(cx);
if (!GetImportArg(cx, args, &importObj)) {
return false;
}
RootedObject instanceProto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WasmInstance,
&instanceProto)) {
return false;
}
if (!instanceProto) {
instanceProto =
GlobalObject::getOrCreatePrototype(cx, JSProto_WasmInstance);
}
Rooted<ImportValues> imports(cx);
if (!GetImports(cx, *module, importObj, imports.address())) {
return false;
}
RootedWasmInstanceObject instanceObj(cx);
if (!module->instantiate(cx, imports.get(), instanceProto, &instanceObj)) {
return false;
}
Log(cx, "sync new Instance() succeeded");
args.rval().setObject(*instanceObj);
return true;
}
Instance& WasmInstanceObject::instance() const {
MOZ_ASSERT(!isNewborn());
return *(Instance*)getReservedSlot(INSTANCE_SLOT).toPrivate();
}
JSObject& WasmInstanceObject::exportsObj() const {
return getReservedSlot(EXPORTS_OBJ_SLOT).toObject();
}
WasmInstanceObject::ExportMap& WasmInstanceObject::exports() const {
return *(ExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
}
WasmInstanceObject::ScopeMap& WasmInstanceObject::scopes() const {
return *(ScopeMap*)getReservedSlot(SCOPES_SLOT).toPrivate();
}
WasmInstanceObject::GlobalObjectVector& WasmInstanceObject::indirectGlobals()
const {
return *(GlobalObjectVector*)getReservedSlot(GLOBALS_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();
const FuncExport& funcExport =
instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex);
unsigned numArgs = funcExport.funcType().args().length();
if (instance.isAsmJS()) {
// 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.
RootedAtom name(cx, instance.getFuncDisplayAtom(cx, funcIndex));
if (!name) {
return false;
}
fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name,
gc::AllocKind::FUNCTION_EXTENDED,
SingletonObject, FunctionFlags::ASMJS_CTOR));
if (!fun) {
return false;
}
// asm.js does not support jit entries.
fun->setWasmFuncIndex(funcIndex);
} else {
RootedAtom name(cx, NumberToAtom(cx, funcIndex));
if (!name) {
return false;
}
fun.set(NewNativeFunction(cx, WasmCall