mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-31 15:48:55 +00:00
18e2173c3d
This resolves #667.
1307 lines
45 KiB
C++
1307 lines
45 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
|
*
|
|
* Copyright 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 mozilla::IsNaN;
|
|
using mozilla::PodZero;
|
|
|
|
static bool
|
|
CloneModule(JSContext* cx, MutableHandle<AsmJSModuleObject*> moduleObj)
|
|
{
|
|
ScopedJSDeletePtr<AsmJSModule> module;
|
|
if (!moduleObj->module().clone(cx, &module))
|
|
return false;
|
|
|
|
module->staticallyLink(cx);
|
|
|
|
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, js_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.hasGetterOrSetterObject())
|
|
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)
|
|
{
|
|
MOZ_ASSERT(global.which() == AsmJSModule::Global::Variable);
|
|
|
|
void* datum = module.globalVarToGlobalDatum(global);
|
|
|
|
switch (global.varInitKind()) {
|
|
case AsmJSModule::Global::InitConstant: {
|
|
const AsmJSNumLit& lit = global.varInitNumLit();
|
|
switch (lit.which()) {
|
|
case AsmJSNumLit::Fixnum:
|
|
case AsmJSNumLit::NegativeInt:
|
|
case AsmJSNumLit::BigUnsigned:
|
|
*(int32_t*)datum = lit.scalarValue().toInt32();
|
|
break;
|
|
case AsmJSNumLit::Double:
|
|
*(double*)datum = lit.scalarValue().toDouble();
|
|
break;
|
|
case AsmJSNumLit::Float:
|
|
*(float*)datum = static_cast<float>(lit.scalarValue().toDouble());
|
|
break;
|
|
case AsmJSNumLit::Int32x4:
|
|
memcpy(datum, lit.simdValue().asInt32x4(), Simd128DataSize);
|
|
break;
|
|
case AsmJSNumLit::Float32x4:
|
|
memcpy(datum, lit.simdValue().asFloat32x4(), Simd128DataSize);
|
|
break;
|
|
case AsmJSNumLit::OutOfRangeInt:
|
|
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("OutOfRangeInt isn't valid in the first place");
|
|
}
|
|
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");
|
|
|
|
SimdConstant simdConstant;
|
|
switch (global.varInitCoercion()) {
|
|
case AsmJS_ToInt32:
|
|
if (!ToInt32(cx, v, (int32_t*)datum))
|
|
return false;
|
|
break;
|
|
case AsmJS_ToNumber:
|
|
if (!ToNumber(cx, v, (double*)datum))
|
|
return false;
|
|
break;
|
|
case AsmJS_FRound:
|
|
if (!RoundFloat32(cx, v, (float*)datum))
|
|
return false;
|
|
break;
|
|
case AsmJS_ToInt32x4:
|
|
if (!ToSimdConstant<Int32x4>(cx, v, &simdConstant))
|
|
return false;
|
|
memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize);
|
|
break;
|
|
case AsmJS_ToFloat32x4:
|
|
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, bool isShared)
|
|
{
|
|
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());
|
|
bool stac = IsSharedTypedArrayConstructor(v, global.viewType());
|
|
if (!((tac || stac) && stac == isShared))
|
|
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, js_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_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;
|
|
}
|
|
|
|
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 (IsDeprecatedAsmJSHeapLength(heapLength)) {
|
|
LinkFail(cx, "ArrayBuffer byteLengths smaller than 64KB are deprecated and "
|
|
"will cause a link-time failure in the future");
|
|
|
|
// The goal of deprecation is to give apps some time before linking
|
|
// fails. However, if warnings-as-errors is turned on (which happens as
|
|
// part of asm.js testing) an exception may be raised.
|
|
if (cx->isExceptionPending())
|
|
return false;
|
|
}
|
|
|
|
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::SharedArrayView:
|
|
case AsmJSModule::Global::ArrayViewCtor:
|
|
if (!ValidateArrayView(cx, global, globalVal, module.hasArrayView() && module.isSharedView()))
|
|
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++)
|
|
module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>();
|
|
|
|
module.initGlobalNaN();
|
|
|
|
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));
|
|
MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(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.numArgs())))
|
|
return false;
|
|
|
|
RootedValue v(cx);
|
|
for (unsigned i = 0; i < func.numArgs(); ++i) {
|
|
v = i < callArgs.length() ? callArgs[i] : UndefinedValue();
|
|
switch (func.argCoercion(i)) {
|
|
case AsmJS_ToInt32:
|
|
if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i]))
|
|
return false;
|
|
break;
|
|
case AsmJS_ToNumber:
|
|
if (!ToNumber(cx, v, (double*)&coercedArgs[i]))
|
|
return false;
|
|
break;
|
|
case AsmJS_FRound:
|
|
if (!RoundFloat32(cx, v, (float*)&coercedArgs[i]))
|
|
return false;
|
|
break;
|
|
case AsmJS_ToInt32x4: {
|
|
SimdConstant simd;
|
|
if (!ToSimdConstant<Int32x4>(cx, v, &simd))
|
|
return false;
|
|
memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize);
|
|
break;
|
|
}
|
|
case AsmJS_ToFloat32x4: {
|
|
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, js_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_ASMJS(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);
|
|
callArgs.rval().set(ObjectValue(*obj));
|
|
return true;
|
|
}
|
|
|
|
JSObject* simdObj;
|
|
switch (func.returnType()) {
|
|
case AsmJSModule::Return_Void:
|
|
callArgs.rval().set(UndefinedValue());
|
|
break;
|
|
case AsmJSModule::Return_Int32:
|
|
callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0]));
|
|
break;
|
|
case AsmJSModule::Return_Double:
|
|
callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0]));
|
|
break;
|
|
case AsmJSModule::Return_Int32x4:
|
|
simdObj = CreateSimd<Int32x4>(cx, (int32_t*)&coercedArgs[0]);
|
|
if (!simdObj)
|
|
return false;
|
|
callArgs.rval().set(ObjectValue(*simdObj));
|
|
break;
|
|
case AsmJSModule::Return_Float32x4:
|
|
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.numArgs();
|
|
JSFunction* fun = NewFunction(cx, NullPtr(), CallAsmJS, numArgs, JSFunction::ASMJS_CTOR,
|
|
cx->global(), name, JSFunction::ExtendedFinalizeKind);
|
|
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;
|
|
|
|
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, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED,
|
|
cx->global(), name, JSFunction::FinalizeKind,
|
|
TenuredObject));
|
|
if (!fun)
|
|
return false;
|
|
|
|
AutoNameVector formals(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())
|
|
.setCompileAndGo(false)
|
|
.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,
|
|
/* enclosingScope = */ NullPtr()))
|
|
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;
|
|
}
|
|
|
|
static bool
|
|
SendBlocksToPerf(JSContext* cx, AsmJSModule& module)
|
|
{
|
|
if (!PerfBlockEnabled())
|
|
return true;
|
|
|
|
unsigned long funcBaseAddress = (unsigned long) module.codeBase();
|
|
const char* filename = module.scriptSource()->filename();
|
|
|
|
for (unsigned i = 0; i < module.numPerfBlocksFunctions(); i++) {
|
|
const AsmJSModule::ProfiledBlocksFunction& func = module.perfProfiledBlocksFunction(i);
|
|
|
|
size_t size = func.pod.endCodeOffset - func.pod.startCodeOffset;
|
|
|
|
JSAutoByteString bytes;
|
|
const char* name = AtomToPrintableString(cx, func.name, &bytes);
|
|
if (!name)
|
|
return false;
|
|
|
|
writePerfSpewerAsmJSBlocksMap(funcBaseAddress, func.pod.startCodeOffset,
|
|
func.endInlineCodeOffset, size, filename, name, func.blocks);
|
|
}
|
|
|
|
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 (module.numExportedFunctions() > 0) {
|
|
size_t firstEntryCode = size_t(module.codeBase() + module.functionBytes());
|
|
writePerfSpewerAsmJSEntriesAndExits(firstEntryCode, module.codeBytes() - module.functionBytes());
|
|
}
|
|
if (!SendBlocksToPerf(cx, module))
|
|
return false;
|
|
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));
|
|
|
|
// All ICache flushing of the module being linked has been inhibited under the
|
|
// assumption that the module is flushed after dynamic linking (when the last code
|
|
// mutation occurs). Thus, enter an AutoFlushICache context for the entire module
|
|
// now. The module range is set below.
|
|
AutoFlushICache afc("LinkAsmJS");
|
|
|
|
// 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()) {
|
|
if (!CloneModule(cx, &moduleObj))
|
|
return false;
|
|
} else {
|
|
// CloneModule already calls setAutoFlushICacheRange internally before patching
|
|
// the cloned module, so avoid calling twice.
|
|
moduleObj->module().setAutoFlushICacheRange();
|
|
}
|
|
|
|
AsmJSModule& module = moduleObj->module();
|
|
|
|
// 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 = NewFunction(cx, NullPtr(), LinkAsmJS, origFun->nargs(),
|
|
flags, NullPtr(), name,
|
|
JSFunction::ExtendedFinalizeKind, TenuredObject);
|
|
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);
|
|
|
|
// Whether the function has been created with a Function ctor
|
|
bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded();
|
|
|
|
if (addParenToLambda && fun->isLambda() && !out.append("("))
|
|
return nullptr;
|
|
|
|
if (!out.append("function "))
|
|
return nullptr;
|
|
|
|
if (fun->atom() && !out.append(fun->atom()))
|
|
return nullptr;
|
|
|
|
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, js_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);
|
|
|
|
// 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 (!out.append("function "))
|
|
return nullptr;
|
|
|
|
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 (!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();
|
|
}
|