https://github.com/mozilla/gecko-dev
Raw File
Tip revision: 771393b303319e834c9b4642866a60708c75bbb4 authored by ffxbld on 15 March 2016, 22:31:00 UTC
Added FIREFOX_45_0_1_RELEASE FIREFOX_45_0_1_BUILD1 tag(s) for changeset e0e51efe7b15. DONTBUILD CLOSED TREE a=release
Tip revision: 771393b
AsmJSLink.cpp
/* -*- 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();
}
back to top