blob: d0e1767ee220e649fcffb75089dc9726a7899d67 [file] [log] [blame]
/* -*- 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 2014 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 "asmjs/AsmJSLink.h"
#include "mozilla/PodOperations.h"
#ifdef MOZ_VTUNE
# include "vtune/VTuneWrapper.h"
#endif
#include "jscntxt.h"
#include "jsmath.h"
#include "jsprf.h"
#include "jswrapper.h"
#include "asmjs/AsmJSModule.h"
#include "builtin/AtomicsObject.h"
#include "builtin/SIMD.h"
#include "frontend/BytecodeCompiler.h"
#include "jit/Ion.h"
#include "jit/JitCommon.h"
#ifdef JS_ION_PERF
# include "jit/PerfSpewer.h"
#endif
#include "vm/ArrayBufferObject.h"
#include "vm/SharedArrayObject.h"
#include "vm/StringBuffer.h"
#include "jsobjinlines.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::IsNaN;
using mozilla::PodZero;
static bool
CloneModule(JSContext* cx, MutableHandle<AsmJSModuleObject*> moduleObj)
{
ScopedJSDeletePtr<AsmJSModule> module;
if (!moduleObj->module().clone(cx, &module))
return false;
AsmJSModuleObject* newModuleObj = AsmJSModuleObject::create(cx, &module);
if (!newModuleObj)
return false;
moduleObj.set(newModuleObj);
return true;
}
static bool
LinkFail(JSContext* cx, const char* str)
{
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, GetErrorMessage,
nullptr, JSMSG_USE_ASM_LINK_FAIL, str);
return false;
}
static bool
GetDataProperty(JSContext* cx, HandleValue objVal, HandlePropertyName field, MutableHandleValue v)
{
if (!objVal.isObject())
return LinkFail(cx, "accessing property of non-object");
RootedObject obj(cx, &objVal.toObject());
if (IsScriptedProxy(obj))
return LinkFail(cx, "accessing property of a Proxy");
Rooted<PropertyDescriptor> desc(cx);
RootedId id(cx, NameToId(field));
if (!GetPropertyDescriptor(cx, obj, id, &desc))
return false;
if (!desc.object())
return LinkFail(cx, "property not present on object");
if (!desc.isDataDescriptor())
return LinkFail(cx, "property is not a data property");
v.set(desc.value());
return true;
}
static bool
HasPureCoercion(JSContext* cx, HandleValue v)
{
if (IsVectorObject<Int32x4>(v) || IsVectorObject<Float32x4>(v))
return true;
// Ideally, we'd reject all non-SIMD non-primitives, but Emscripten has a
// bug that generates code that passes functions for some imports. To avoid
// breaking all the code that contains this bug, we make an exception for
// functions that don't have user-defined valueOf or toString, for their
// coercions are not observable and coercion via ToNumber/ToInt32
// definitely produces NaN/0. We should remove this special case later once
// most apps have been built with newer Emscripten.
jsid toString = NameToId(cx->names().toString);
if (v.toObject().is<JSFunction>() &&
HasObjectValueOf(&v.toObject(), cx) &&
ClassMethodIsNative(cx, &v.toObject().as<JSFunction>(), &JSFunction::class_, toString, fun_toString))
{
return true;
}
return false;
}
static bool
ValidateGlobalVariable(JSContext* cx, const AsmJSModule& module, AsmJSModule::Global& global,
HandleValue importVal)
{
void* datum = module.globalData() + global.varGlobalDataOffset();
switch (global.varInitKind()) {
case AsmJSModule::Global::InitConstant: {
Val v = global.varInitVal();
switch (v.type()) {
case ValType::I32:
*(int32_t*)datum = v.i32();
break;
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32:
*(float*)datum = v.f32();
break;
case ValType::F64:
*(double*)datum = v.f64();
break;
case ValType::I32x4:
memcpy(datum, v.i32x4(), Simd128DataSize);
break;
case ValType::F32x4:
memcpy(datum, v.f32x4(), Simd128DataSize);
break;
}
break;
}
case AsmJSModule::Global::InitImport: {
RootedPropertyName field(cx, global.varImportField());
RootedValue v(cx);
if (!GetDataProperty(cx, importVal, field, &v))
return false;
if (!v.isPrimitive() && !HasPureCoercion(cx, v))
return LinkFail(cx, "Imported values must be primitives");
switch (global.varInitImportType()) {
case ValType::I32:
if (!ToInt32(cx, v, (int32_t*)datum))
return false;
break;
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32:
if (!RoundFloat32(cx, v, (float*)datum))
return false;
break;
case ValType::F64:
if (!ToNumber(cx, v, (double*)datum))
return false;
break;
case ValType::I32x4: {
SimdConstant simdConstant;
if (!ToSimdConstant<Int32x4>(cx, v, &simdConstant))
return false;
memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize);
break;
}
case ValType::F32x4: {
SimdConstant simdConstant;
if (!ToSimdConstant<Float32x4>(cx, v, &simdConstant))
return false;
memcpy(datum, simdConstant.asFloat32x4(), Simd128DataSize);
break;
}
}
break;
}
}
return true;
}
static bool
ValidateFFI(JSContext* cx, AsmJSModule::Global& global, HandleValue importVal,
AutoObjectVector* ffis)
{
RootedPropertyName field(cx, global.ffiField());
RootedValue v(cx);
if (!GetDataProperty(cx, importVal, field, &v))
return false;
if (!v.isObject() || !v.toObject().is<JSFunction>())
return LinkFail(cx, "FFI imports must be functions");
(*ffis)[global.ffiIndex()].set(&v.toObject().as<JSFunction>());
return true;
}
static bool
ValidateArrayView(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
RootedPropertyName field(cx, global.maybeViewName());
if (!field)
return true;
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, field, &v))
return false;
bool tac = IsTypedArrayConstructor(v, global.viewType());
if (!tac)
return LinkFail(cx, "bad typed array constructor");
return true;
}
static bool
ValidateByteLength(JSContext* cx, HandleValue globalVal)
{
RootedPropertyName field(cx, cx->names().byteLength);
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, field, &v))
return false;
if (!v.isObject() || !v.toObject().isBoundFunction())
return LinkFail(cx, "byteLength must be a bound function object");
RootedFunction fun(cx, &v.toObject().as<JSFunction>());
RootedValue boundTarget(cx, ObjectValue(*fun->getBoundFunctionTarget()));
if (!IsNativeFunction(boundTarget, fun_call))
return LinkFail(cx, "bound target of byteLength must be Function.prototype.call");
RootedValue boundThis(cx, fun->getBoundFunctionThis());
if (!IsNativeFunction(boundThis, ArrayBufferObject::byteLengthGetter))
return LinkFail(cx, "bound this value must be ArrayBuffer.protototype.byteLength accessor");
return true;
}
static bool
ValidateMathBuiltinFunction(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, cx->names().Math, &v))
return false;
RootedPropertyName field(cx, global.mathName());
if (!GetDataProperty(cx, v, field, &v))
return false;
Native native = nullptr;
switch (global.mathBuiltinFunction()) {
case AsmJSMathBuiltin_sin: native = math_sin; break;
case AsmJSMathBuiltin_cos: native = math_cos; break;
case AsmJSMathBuiltin_tan: native = math_tan; break;
case AsmJSMathBuiltin_asin: native = math_asin; break;
case AsmJSMathBuiltin_acos: native = math_acos; break;
case AsmJSMathBuiltin_atan: native = math_atan; break;
case AsmJSMathBuiltin_ceil: native = math_ceil; break;
case AsmJSMathBuiltin_floor: native = math_floor; break;
case AsmJSMathBuiltin_exp: native = math_exp; break;
case AsmJSMathBuiltin_log: native = math_log; break;
case AsmJSMathBuiltin_pow: native = math_pow; break;
case AsmJSMathBuiltin_sqrt: native = math_sqrt; break;
case AsmJSMathBuiltin_min: native = math_min; break;
case AsmJSMathBuiltin_max: native = math_max; break;
case AsmJSMathBuiltin_abs: native = math_abs; break;
case AsmJSMathBuiltin_atan2: native = math_atan2; break;
case AsmJSMathBuiltin_imul: native = math_imul; break;
case AsmJSMathBuiltin_clz32: native = math_clz32; break;
case AsmJSMathBuiltin_fround: native = math_fround; break;
}
if (!IsNativeFunction(v, native))
return LinkFail(cx, "bad Math.* builtin function");
return true;
}
static PropertyName*
SimdTypeToName(JSContext* cx, AsmJSSimdType type)
{
switch (type) {
case AsmJSSimdType_int32x4: return cx->names().int32x4;
case AsmJSSimdType_float32x4: return cx->names().float32x4;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected SIMD type");
}
static SimdTypeDescr::Type
AsmJSSimdTypeToTypeDescrType(AsmJSSimdType type)
{
switch (type) {
case AsmJSSimdType_int32x4: return Int32x4::type;
case AsmJSSimdType_float32x4: return Float32x4::type;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected AsmJSSimdType");
}
static bool
ValidateSimdType(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal,
MutableHandleValue out)
{
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, cx->names().SIMD, &v))
return false;
AsmJSSimdType type;
if (global.which() == AsmJSModule::Global::SimdCtor)
type = global.simdCtorType();
else
type = global.simdOperationType();
RootedPropertyName simdTypeName(cx, SimdTypeToName(cx, type));
if (!GetDataProperty(cx, v, simdTypeName, &v))
return false;
if (!v.isObject())
return LinkFail(cx, "bad SIMD type");
RootedObject simdDesc(cx, &v.toObject());
if (!simdDesc->is<SimdTypeDescr>())
return LinkFail(cx, "bad SIMD type");
if (AsmJSSimdTypeToTypeDescrType(type) != simdDesc->as<SimdTypeDescr>().type())
return LinkFail(cx, "bad SIMD type");
out.set(v);
return true;
}
static bool
ValidateSimdType(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
RootedValue _(cx);
return ValidateSimdType(cx, global, globalVal, &_);
}
static bool
ValidateSimdOperation(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
// SIMD operations are loaded from the SIMD type, so the type must have been
// validated before the operation.
RootedValue v(cx);
JS_ALWAYS_TRUE(ValidateSimdType(cx, global, globalVal, &v));
RootedPropertyName opName(cx, global.simdOperationName());
if (!GetDataProperty(cx, v, opName, &v))
return false;
Native native = nullptr;
switch (global.simdOperationType()) {
#define SET_NATIVE_INT32X4(op) case AsmJSSimdOperation_##op: native = simd_int32x4_##op; break;
#define SET_NATIVE_FLOAT32X4(op) case AsmJSSimdOperation_##op: native = simd_float32x4_##op; break;
#define FALLTHROUGH(op) case AsmJSSimdOperation_##op:
case AsmJSSimdType_int32x4:
switch (global.simdOperation()) {
FOREACH_INT32X4_SIMD_OP(SET_NATIVE_INT32X4)
FOREACH_COMMONX4_SIMD_OP(SET_NATIVE_INT32X4)
FOREACH_FLOAT32X4_SIMD_OP(FALLTHROUGH)
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("shouldn't have been validated in the first "
"place");
}
break;
case AsmJSSimdType_float32x4:
switch (global.simdOperation()) {
FOREACH_FLOAT32X4_SIMD_OP(SET_NATIVE_FLOAT32X4)
FOREACH_COMMONX4_SIMD_OP(SET_NATIVE_FLOAT32X4)
FOREACH_INT32X4_SIMD_OP(FALLTHROUGH)
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("shouldn't have been validated in the first "
"place");
}
break;
#undef FALLTHROUGH
#undef SET_NATIVE_FLOAT32X4
#undef SET_NATIVE_INT32X4
#undef SET_NATIVE
}
if (!native || !IsNativeFunction(v, native))
return LinkFail(cx, "bad SIMD.type.* operation");
return true;
}
static bool
ValidateAtomicsBuiltinFunction(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, cx->names().Atomics, &v))
return false;
RootedPropertyName field(cx, global.atomicsName());
if (!GetDataProperty(cx, v, field, &v))
return false;
Native native = nullptr;
switch (global.atomicsBuiltinFunction()) {
case AsmJSAtomicsBuiltin_compareExchange: native = atomics_compareExchange; break;
case AsmJSAtomicsBuiltin_exchange: native = atomics_exchange; break;
case AsmJSAtomicsBuiltin_load: native = atomics_load; break;
case AsmJSAtomicsBuiltin_store: native = atomics_store; break;
case AsmJSAtomicsBuiltin_fence: native = atomics_fence; break;
case AsmJSAtomicsBuiltin_add: native = atomics_add; break;
case AsmJSAtomicsBuiltin_sub: native = atomics_sub; break;
case AsmJSAtomicsBuiltin_and: native = atomics_and; break;
case AsmJSAtomicsBuiltin_or: native = atomics_or; break;
case AsmJSAtomicsBuiltin_xor: native = atomics_xor; break;
case AsmJSAtomicsBuiltin_isLockFree: native = atomics_isLockFree; break;
}
if (!IsNativeFunction(v, native))
return LinkFail(cx, "bad Atomics.* builtin function");
return true;
}
static bool
ValidateConstant(JSContext* cx, AsmJSModule::Global& global, HandleValue globalVal)
{
RootedPropertyName field(cx, global.constantName());
RootedValue v(cx, globalVal);
if (global.constantKind() == AsmJSModule::Global::MathConstant) {
if (!GetDataProperty(cx, v, cx->names().Math, &v))
return false;
}
if (!GetDataProperty(cx, v, field, &v))
return false;
if (!v.isNumber())
return LinkFail(cx, "math / global constant value needs to be a number");
// NaN != NaN
if (IsNaN(global.constantValue())) {
if (!IsNaN(v.toNumber()))
return LinkFail(cx, "global constant value needs to be NaN");
} else {
if (v.toNumber() != global.constantValue())
return LinkFail(cx, "global constant value mismatch");
}
return true;
}
static bool
LinkModuleToHeap(JSContext* cx, AsmJSModule& module, Handle<ArrayBufferObjectMaybeShared*> heap)
{
uint32_t heapLength = heap->byteLength();
if (!IsValidAsmJSHeapLength(heapLength)) {
ScopedJSFreePtr<char> msg(
JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next "
"valid length is 0x%x",
heapLength,
RoundUpToNextValidAsmJSHeapLength(heapLength)));
return LinkFail(cx, msg.get());
}
// This check is sufficient without considering the size of the loaded datum because heap
// loads and stores start on an aligned boundary and the heap byteLength has larger alignment.
MOZ_ASSERT((module.minHeapLength() - 1) <= INT32_MAX);
if (heapLength < module.minHeapLength()) {
ScopedJSFreePtr<char> msg(
JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied "
"by const heap accesses and/or change-heap minimum-length requirements).",
heapLength,
module.minHeapLength()));
return LinkFail(cx, msg.get());
}
if (heapLength > module.maxHeapLength()) {
ScopedJSFreePtr<char> msg(
JS_smprintf("ArrayBuffer byteLength 0x%x is greater than maximum length of 0x%x",
heapLength,
module.maxHeapLength()));
return LinkFail(cx, msg.get());
}
// If we've generated the code with signal handlers in mind (for bounds
// checks on x64 and for interrupt callback requesting on all platforms),
// we need to be able to use signals at runtime. In particular, a module
// can have been created using signals and cached, and executed without
// signals activated.
if (module.usesSignalHandlersForInterrupt() && !cx->canUseSignalHandlers())
return LinkFail(cx, "Code generated with signal handlers but signals are deactivated");
if (heap->is<ArrayBufferObject>()) {
Rooted<ArrayBufferObject*> abheap(cx, &heap->as<ArrayBufferObject>());
if (!ArrayBufferObject::prepareForAsmJS(cx, abheap, module.usesSignalHandlersForOOB()))
return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
}
module.initHeap(heap, cx);
return true;
}
static bool
DynamicallyLinkModule(JSContext* cx, const CallArgs& args, AsmJSModule& module)
{
module.setIsDynamicallyLinked(cx->runtime());
HandleValue globalVal = args.get(0);
HandleValue importVal = args.get(1);
HandleValue bufferVal = args.get(2);
Rooted<ArrayBufferObjectMaybeShared*> heap(cx);
if (module.hasArrayView()) {
if (module.isSharedView() && !IsSharedArrayBuffer(bufferVal))
return LinkFail(cx, "shared views can only be constructed onto SharedArrayBuffer");
if (!module.isSharedView() && !IsArrayBuffer(bufferVal))
return LinkFail(cx, "unshared views can only be constructed onto ArrayBuffer");
heap = &AsAnyArrayBuffer(bufferVal);
if (!LinkModuleToHeap(cx, module, heap))
return false;
}
AutoObjectVector ffis(cx);
if (!ffis.resize(module.numFFIs()))
return false;
for (unsigned i = 0; i < module.numGlobals(); i++) {
AsmJSModule::Global& global = module.global(i);
switch (global.which()) {
case AsmJSModule::Global::Variable:
if (!ValidateGlobalVariable(cx, module, global, importVal))
return false;
break;
case AsmJSModule::Global::FFI:
if (!ValidateFFI(cx, global, importVal, &ffis))
return false;
break;
case AsmJSModule::Global::ArrayView:
case AsmJSModule::Global::ArrayViewCtor:
if (!ValidateArrayView(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::ByteLength:
if (!ValidateByteLength(cx, globalVal))
return false;
break;
case AsmJSModule::Global::MathBuiltinFunction:
if (!ValidateMathBuiltinFunction(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::AtomicsBuiltinFunction:
if (!ValidateAtomicsBuiltinFunction(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::Constant:
if (!ValidateConstant(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::SimdCtor:
if (!ValidateSimdType(cx, global, globalVal))
return false;
break;
case AsmJSModule::Global::SimdOperation:
if (!ValidateSimdOperation(cx, global, globalVal))
return false;
break;
}
}
for (unsigned i = 0; i < module.numExits(); i++) {
const AsmJSModule::Exit& exit = module.exit(i);
exit.datum(module).fun = &ffis[exit.ffiIndex()]->as<JSFunction>();
}
// See the comment in AllocateExecutableMemory.
ExecutableAllocator::makeExecutable(module.codeBase(), module.codeBytes());
return true;
}
static bool
ChangeHeap(JSContext* cx, AsmJSModule& module, const CallArgs& args)
{
HandleValue bufferArg = args.get(0);
if (!IsArrayBuffer(bufferArg)) {
ReportIncompatible(cx, args);
return false;
}
Rooted<ArrayBufferObject*> newBuffer(cx, &bufferArg.toObject().as<ArrayBufferObject>());
uint32_t heapLength = newBuffer->byteLength();
if (heapLength & module.heapLengthMask() ||
heapLength < module.minHeapLength() ||
heapLength > module.maxHeapLength())
{
args.rval().set(BooleanValue(false));
return true;
}
if (!module.hasArrayView()) {
args.rval().set(BooleanValue(true));
return true;
}
MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength));
if (!ArrayBufferObject::prepareForAsmJS(cx, newBuffer, module.usesSignalHandlersForOOB()))
return false;
args.rval().set(BooleanValue(module.changeHeap(newBuffer, cx)));
return true;
}
// An asm.js function stores, in its extended slots:
// - a pointer to the module from which it was returned
// - its index in the ordered list of exported functions
static const unsigned ASM_MODULE_SLOT = 0;
static const unsigned ASM_EXPORT_INDEX_SLOT = 1;
static unsigned
FunctionToExportedFunctionIndex(HandleFunction fun)
{
MOZ_ASSERT(IsAsmJSFunction(fun));
Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT);
return v.toInt32();
}
static const AsmJSModule::ExportedFunction&
FunctionToExportedFunction(HandleFunction fun, AsmJSModule& module)
{
unsigned funIndex = FunctionToExportedFunctionIndex(fun);
return module.exportedFunction(funIndex);
}
static AsmJSModule&
FunctionToEnclosingModule(HandleFunction fun)
{
return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as<AsmJSModuleObject>().module();
}
// This is the js::Native for functions exported by an asm.js module.
static bool
CallAsmJS(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs callArgs = CallArgsFromVp(argc, vp);
RootedFunction callee(cx, &callArgs.callee().as<JSFunction>());
AsmJSModule& module = FunctionToEnclosingModule(callee);
const AsmJSModule::ExportedFunction& func = FunctionToExportedFunction(callee, module);
// The heap-changing function is a special-case and is implemented by C++.
if (func.isChangeHeap())
return ChangeHeap(cx, module, callArgs);
// Enable/disable profiling in the asm.js module to match the current global
// profiling state. Don't do this if the module is already active on the
// stack since this would leave the module in a state where profiling is
// enabled but the stack isn't unwindable.
if (module.profilingEnabled() != cx->runtime()->spsProfiler.enabled() && !module.active())
module.setProfilingEnabled(cx->runtime()->spsProfiler.enabled(), cx);
// The calling convention for an external call into asm.js is to pass an
// array of 16-byte values where each value contains either a coerced int32
// (in the low word), a double value (in the low dword) or a SIMD vector
// value, with the coercions specified by the asm.js signature. The
// external entry point unpacks this array into the system-ABI-specified
// registers and stack memory and then calls into the internal entry point.
// The return value is stored in the first element of the array (which,
// therefore, must have length >= 1).
js::Vector<AsmJSModule::EntryArg, 8> coercedArgs(cx);
if (!coercedArgs.resize(Max<size_t>(1, func.sig().args().length())))
return false;
RootedValue v(cx);
for (unsigned i = 0; i < func.sig().args().length(); ++i) {
v = i < callArgs.length() ? callArgs[i] : UndefinedValue();
switch (func.sig().arg(i)) {
case ValType::I32:
if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i]))
return false;
break;
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32:
if (!RoundFloat32(cx, v, (float*)&coercedArgs[i]))
return false;
break;
case ValType::F64:
if (!ToNumber(cx, v, (double*)&coercedArgs[i]))
return false;
break;
case ValType::I32x4: {
SimdConstant simd;
if (!ToSimdConstant<Int32x4>(cx, v, &simd))
return false;
memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize);
break;
}
case ValType::F32x4: {
SimdConstant simd;
if (!ToSimdConstant<Float32x4>(cx, v, &simd))
return false;
memcpy(&coercedArgs[i], simd.asFloat32x4(), Simd128DataSize);
break;
}
}
}
// The correct way to handle this situation would be to allocate a new range
// of PROT_NONE memory and module.changeHeap to this memory. That would
// cause every access to take the out-of-bounds signal-handler path which
// does the right thing. For now, just throw an out-of-memory exception
// since these can technically pop out anywhere and the full fix may
// actually OOM when trying to allocate the PROT_NONE memory.
if (module.hasDetachedHeap()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_OUT_OF_MEMORY);
return false;
}
{
// Push an AsmJSActivation to describe the asm.js frames we're about to
// push when running this module. Additionally, push a JitActivation so
// that the optimized asm.js-to-Ion FFI call path (which we want to be
// very fast) can avoid doing so. The JitActivation is marked as
// inactive so stack iteration will skip over it.
AsmJSActivation activation(cx, module);
JitActivation jitActivation(cx, /* active */ false);
// Call the per-exported-function trampoline created by GenerateEntry.
AsmJSModule::CodePtr enter = module.entryTrampoline(func);
if (!CALL_GENERATED_2(enter, coercedArgs.begin(), module.globalData()))
return false;
}
if (callArgs.isConstructing()) {
// By spec, when a function is called as a constructor and this function
// returns a primary type, which is the case for all asm.js exported
// functions, the returned value is discarded and an empty object is
// returned instead.
PlainObject* obj = NewBuiltinClassInstance<PlainObject>(cx);
if (!obj)
return false;
callArgs.rval().set(ObjectValue(*obj));
return true;
}
JSObject* simdObj;
switch (func.sig().ret()) {
case ExprType::Void:
callArgs.rval().set(UndefinedValue());
break;
case ExprType::I32:
callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0]));
break;
case ExprType::I64:
MOZ_CRASH("int64");
case ExprType::F32:
case ExprType::F64:
callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0]));
break;
case ExprType::I32x4:
simdObj = CreateSimd<Int32x4>(cx, (int32_t*)&coercedArgs[0]);
if (!simdObj)
return false;
callArgs.rval().set(ObjectValue(*simdObj));
break;
case ExprType::F32x4:
simdObj = CreateSimd<Float32x4>(cx, (float*)&coercedArgs[0]);
if (!simdObj)
return false;
callArgs.rval().set(ObjectValue(*simdObj));
break;
}
return true;
}
static JSFunction*
NewExportedFunction(JSContext* cx, const AsmJSModule::ExportedFunction& func,
HandleObject moduleObj, unsigned exportIndex)
{
RootedPropertyName name(cx, func.name());
unsigned numArgs = func.isChangeHeap() ? 1 : func.sig().args().length();
JSFunction* fun =
NewNativeConstructor(cx, CallAsmJS, numArgs, name,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject,
JSFunction::ASMJS_CTOR);
if (!fun)
return nullptr;
fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj));
fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
return fun;
}
static bool
HandleDynamicLinkFailure(JSContext* cx, const CallArgs& args, AsmJSModule& module,
HandlePropertyName name)
{
if (cx->isExceptionPending())
return false;
// Source discarding is allowed to affect JS semantics because it is never
// enabled for normal JS content.
bool haveSource = module.scriptSource()->hasSourceData();
if (!haveSource && !JSScript::loadSource(cx, module.scriptSource(), &haveSource))
return false;
if (!haveSource) {
JS_ReportError(cx, "asm.js link failure with source discarding enabled");
return false;
}
uint32_t begin = module.srcBodyStart(); // starts right after 'use asm'
uint32_t end = module.srcEndBeforeCurly();
Rooted<JSFlatString*> src(cx, module.scriptSource()->substringDontDeflate(cx, begin, end));
if (!src)
return false;
RootedFunction fun(cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL,
name, gc::AllocKind::FUNCTION,
TenuredObject));
if (!fun)
return false;
Rooted<PropertyNameVector> formals(cx, PropertyNameVector(cx));
if (!formals.reserve(3))
return false;
if (module.globalArgumentName())
formals.infallibleAppend(module.globalArgumentName());
if (module.importArgumentName())
formals.infallibleAppend(module.importArgumentName());
if (module.bufferArgumentName())
formals.infallibleAppend(module.bufferArgumentName());
CompileOptions options(cx);
options.setMutedErrors(module.scriptSource()->mutedErrors())
.setFile(module.scriptSource()->filename())
.setNoScriptRval(false);
// The exported function inherits an implicit strict context if the module
// also inherited it somehow.
if (module.strict())
options.strictOption = true;
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, src))
return false;
const char16_t* chars = stableChars.twoByteRange().start().get();
SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller()
? SourceBufferHolder::GiveOwnership
: SourceBufferHolder::NoOwnership;
SourceBufferHolder srcBuf(chars, end - begin, ownership);
if (!frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf))
return false;
// Call the function we just recompiled.
args.setCallee(ObjectValue(*fun));
return Invoke(cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT);
}
#ifdef MOZ_VTUNE
static bool
SendFunctionsToVTune(JSContext* cx, AsmJSModule& module)
{
uint8_t* base = module.codeBase();
for (unsigned i = 0; i < module.numProfiledFunctions(); i++) {
const AsmJSModule::ProfiledFunction& func = module.profiledFunction(i);
uint8_t* start = base + func.pod.startCodeOffset;
uint8_t* end = base + func.pod.endCodeOffset;
MOZ_ASSERT(end >= start);
unsigned method_id = iJIT_GetNewMethodID();
if (method_id == 0)
return false;
JSAutoByteString bytes;
const char* method_name = AtomToPrintableString(cx, func.name, &bytes);
if (!method_name)
return false;
iJIT_Method_Load method;
method.method_id = method_id;
method.method_name = const_cast<char*>(method_name);
method.method_load_address = (void*)start;
method.method_size = unsigned(end - start);
method.line_number_size = 0;
method.line_number_table = nullptr;
method.class_id = 0;
method.class_file_name = nullptr;
method.source_file_name = nullptr;
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&method);
}
return true;
}
#endif
#ifdef JS_ION_PERF
static bool
SendFunctionsToPerf(JSContext* cx, AsmJSModule& module)
{
if (!PerfFuncEnabled())
return true;
uintptr_t base = (uintptr_t) module.codeBase();
const char* filename = module.scriptSource()->filename();
for (unsigned i = 0; i < module.numProfiledFunctions(); i++) {
const AsmJSModule::ProfiledFunction& func = module.profiledFunction(i);
uintptr_t start = base + (unsigned long) func.pod.startCodeOffset;
uintptr_t end = base + (unsigned long) func.pod.endCodeOffset;
MOZ_ASSERT(end >= start);
size_t size = end - start;
JSAutoByteString bytes;
const char* name = AtomToPrintableString(cx, func.name, &bytes);
if (!name)
return false;
writePerfSpewerAsmJSFunctionMap(start, size, filename, func.pod.lineno,
func.pod.columnIndex, name);
}
return true;
}
#endif
static bool
SendModuleToAttachedProfiler(JSContext* cx, AsmJSModule& module)
{
#if defined(MOZ_VTUNE)
if (IsVTuneProfilingActive() && !SendFunctionsToVTune(cx, module))
return false;
#endif
#if defined(JS_ION_PERF)
if (!SendFunctionsToPerf(cx, module))
return false;
#endif
return true;
}
static JSObject*
CreateExportObject(JSContext* cx, Handle<AsmJSModuleObject*> moduleObj)
{
AsmJSModule& module = moduleObj->module();
if (module.numExportedFunctions() == 1) {
const AsmJSModule::ExportedFunction& func = module.exportedFunction(0);
if (!func.maybeFieldName())
return NewExportedFunction(cx, func, moduleObj, 0);
}
gc::AllocKind allocKind = gc::GetGCObjectKind(module.numExportedFunctions());
RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind));
if (!obj)
return nullptr;
for (unsigned i = 0; i < module.numExportedFunctions(); i++) {
const AsmJSModule::ExportedFunction& func = module.exportedFunction(i);
RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, i));
if (!fun)
return nullptr;
MOZ_ASSERT(func.maybeFieldName() != nullptr);
RootedId id(cx, NameToId(func.maybeFieldName()));
RootedValue val(cx, ObjectValue(*fun));
if (!NativeDefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE))
return nullptr;
}
return obj;
}
static const unsigned MODULE_FUN_SLOT = 0;
static AsmJSModuleObject&
ModuleFunctionToModuleObject(JSFunction* fun)
{
return fun->getExtendedSlot(MODULE_FUN_SLOT).toObject().as<AsmJSModuleObject>();
}
// Implements the semantics of an asm.js module function that has been successfully validated.
static bool
LinkAsmJS(JSContext* cx, unsigned argc, JS::Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// The LinkAsmJS builtin (created by NewAsmJSModuleFunction) is an extended
// function and stores its module in an extended slot.
RootedFunction fun(cx, &args.callee().as<JSFunction>());
Rooted<AsmJSModuleObject*> moduleObj(cx, &ModuleFunctionToModuleObject(fun));
// When a module is linked, it is dynamically specialized to the given
// arguments (buffer, ffis). Thus, if the module is linked again (it is just
// a function so it can be called multiple times), we need to clone a new
// module.
if (moduleObj->module().isDynamicallyLinked() && !CloneModule(cx, &moduleObj))
return false;
AsmJSModule& module = moduleObj->module();
AutoFlushICache afc("LinkAsmJS");
module.setAutoFlushICacheRange();
// Link the module by performing the link-time validation checks in the
// asm.js spec and then patching the generated module to associate it with
// the given heap (ArrayBuffer) and a new global data segment (the closure
// state shared by the inner asm.js functions).
if (!DynamicallyLinkModule(cx, args, module)) {
// Linking failed, so reparse the entire asm.js module from scratch to
// get normal interpreted bytecode which we can simply Invoke. Very slow.
RootedPropertyName name(cx, fun->name());
return HandleDynamicLinkFailure(cx, args, module, name);
}
// Notify profilers so that asm.js generated code shows up with JS function
// names and lines in native (i.e., not SPS) profilers.
if (!SendModuleToAttachedProfiler(cx, module))
return false;
// Link-time validation succeeded, so wrap all the exported functions with
// CallAsmJS builtins that trampoline into the generated code.
JSObject* obj = CreateExportObject(cx, moduleObj);
if (!obj)
return false;
args.rval().set(ObjectValue(*obj));
return true;
}
JSFunction*
js::NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj)
{
RootedPropertyName name(cx, origFun->name());
JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR
: JSFunction::ASMJS_CTOR;
JSFunction* moduleFun =
NewNativeConstructor(cx, LinkAsmJS, origFun->nargs(), name,
gc::AllocKind::FUNCTION_EXTENDED, TenuredObject,
flags);
if (!moduleFun)
return nullptr;
moduleFun->setExtendedSlot(MODULE_FUN_SLOT, ObjectValue(*moduleObj));
return moduleFun;
}
bool
js::IsAsmJSModuleNative(js::Native native)
{
return native == LinkAsmJS;
}
static bool
IsMaybeWrappedNativeFunction(const Value& v, Native native, JSFunction** fun = nullptr)
{
if (!v.isObject())
return false;
JSObject* obj = CheckedUnwrap(&v.toObject());
if (!obj)
return false;
if (!obj->is<JSFunction>())
return false;
if (fun)
*fun = &obj->as<JSFunction>();
return obj->as<JSFunction>().maybeNative() == native;
}
bool
js::IsAsmJSModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args.get(0), LinkAsmJS);
args.rval().set(BooleanValue(rval));
return true;
}
bool
js::IsAsmJSModule(HandleFunction fun)
{
return fun->isNative() && fun->maybeNative() == LinkAsmJS;
}
static bool
AppendUseStrictSource(JSContext* cx, HandleFunction fun, Handle<JSFlatString*> src, StringBuffer& out)
{
// We need to add "use strict" in the body right after the opening
// brace.
size_t bodyStart = 0, bodyEnd;
// No need to test for functions created with the Function ctor as
// these don't implicitly inherit the "use strict" context. Strict mode is
// enabled for functions created with the Function ctor only if they begin with
// the "use strict" directive, but these functions won't validate as asm.js
// modules.
if (!FindBody(cx, fun, src, &bodyStart, &bodyEnd))
return false;
return out.appendSubstring(src, 0, bodyStart) &&
out.append("\n\"use strict\";\n") &&
out.appendSubstring(src, bodyStart, src->length() - bodyStart);
}
JSString*
js::AsmJSModuleToString(JSContext* cx, HandleFunction fun, bool addParenToLambda)
{
AsmJSModule& module = ModuleFunctionToModuleObject(fun).module();
uint32_t begin = module.srcStart();
uint32_t end = module.srcEndAfterCurly();
ScriptSource* source = module.scriptSource();
StringBuffer out(cx);
if (addParenToLambda && fun->isLambda() && !out.append("("))
return nullptr;
if (!out.append("function "))
return nullptr;
if (fun->atom() && !out.append(fun->atom()))
return nullptr;
bool haveSource = source->hasSourceData();
if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
return nullptr;
if (!haveSource) {
if (!out.append("() {\n [sourceless code]\n}"))
return nullptr;
} else {
// Whether the function has been created with a Function ctor
bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded();
if (funCtor) {
// Functions created with the function constructor don't have arguments in their source.
if (!out.append("("))
return nullptr;
if (PropertyName* argName = module.globalArgumentName()) {
if (!out.append(argName))
return nullptr;
}
if (PropertyName* argName = module.importArgumentName()) {
if (!out.append(", ") || !out.append(argName))
return nullptr;
}
if (PropertyName* argName = module.bufferArgumentName()) {
if (!out.append(", ") || !out.append(argName))
return nullptr;
}
if (!out.append(") {\n"))
return nullptr;
}
Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
if (!src)
return nullptr;
if (module.strict()) {
if (!AppendUseStrictSource(cx, fun, src, out))
return nullptr;
} else {
if (!out.append(src))
return nullptr;
}
if (funCtor && !out.append("\n}"))
return nullptr;
}
if (addParenToLambda && fun->isLambda() && !out.append(")"))
return nullptr;
return out.finishString();
}
bool
js::IsAsmJSModuleLoadedFromCache(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* fun;
if (!args.hasDefined(0) || !IsMaybeWrappedNativeFunction(args[0], LinkAsmJS, &fun)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_USE_ASM_TYPE_FAIL,
"argument passed to isAsmJSModuleLoadedFromCache is not a "
"validated asm.js module");
return false;
}
bool loadedFromCache = ModuleFunctionToModuleObject(fun).module().loadedFromCache();
args.rval().set(BooleanValue(loadedFromCache));
return true;
}
bool
js::IsAsmJSFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args[0], CallAsmJS);
args.rval().set(BooleanValue(rval));
return true;
}
bool
js::IsAsmJSFunction(HandleFunction fun)
{
return fun->isNative() && fun->maybeNative() == CallAsmJS;
}
JSString*
js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun)
{
AsmJSModule& module = FunctionToEnclosingModule(fun);
const AsmJSModule::ExportedFunction& f = FunctionToExportedFunction(fun, module);
uint32_t begin = module.srcStart() + f.startOffsetInModule();
uint32_t end = module.srcStart() + f.endOffsetInModule();
ScriptSource* source = module.scriptSource();
StringBuffer out(cx);
if (!out.append("function "))
return nullptr;
bool haveSource = source->hasSourceData();
if (!haveSource && !JSScript::loadSource(cx, source, &haveSource))
return nullptr;
if (!haveSource) {
// asm.js functions can't be anonymous
MOZ_ASSERT(fun->atom());
if (!out.append(fun->atom()))
return nullptr;
if (!out.append("() {\n [sourceless code]\n}"))
return nullptr;
} else {
// asm.js functions cannot have been created with a Function constructor
// as they belong within a module.
MOZ_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded()));
if (module.strict()) {
// AppendUseStrictSource expects its input to start right after the
// function name, so split the source chars from the src into two parts:
// the function name and the rest (arguments + body).
// asm.js functions can't be anonymous
MOZ_ASSERT(fun->atom());
if (!out.append(fun->atom()))
return nullptr;
size_t nameEnd = begin + fun->atom()->length();
Rooted<JSFlatString*> src(cx, source->substring(cx, nameEnd, end));
if (!src || !AppendUseStrictSource(cx, fun, src, out))
return nullptr;
} else {
Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
if (!src)
return nullptr;
if (!out.append(src))
return nullptr;
}
}
return out.finishString();
}