michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jit/AsmJSLink.h" michael@0: michael@0: #include "mozilla/BinarySearch.h" michael@0: #include "mozilla/PodOperations.h" michael@0: michael@0: #ifdef MOZ_VTUNE michael@0: # include "vtune/VTuneWrapper.h" michael@0: #endif michael@0: michael@0: #include "jscntxt.h" michael@0: #include "jsmath.h" michael@0: #include "jsprf.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "frontend/BytecodeCompiler.h" michael@0: #include "jit/AsmJSModule.h" michael@0: #include "jit/Ion.h" michael@0: #include "jit/JitCommon.h" michael@0: #ifdef JS_ION_PERF michael@0: # include "jit/PerfSpewer.h" michael@0: #endif michael@0: #include "vm/StringBuffer.h" michael@0: michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::jit; michael@0: michael@0: using mozilla::BinarySearch; michael@0: using mozilla::IsNaN; michael@0: using mozilla::PodZero; michael@0: michael@0: AsmJSFrameIterator::AsmJSFrameIterator(const AsmJSActivation *activation) michael@0: { michael@0: if (!activation || activation->isInterruptedSP()) { michael@0: PodZero(this); michael@0: JS_ASSERT(done()); michael@0: return; michael@0: } michael@0: michael@0: module_ = &activation->module(); michael@0: sp_ = activation->exitSP(); michael@0: michael@0: #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) michael@0: // For calls to Ion/C++ on x86/x64, the exitSP is the SP right before the call michael@0: // to C++. Since the call instruction pushes the return address, we know michael@0: // that the return address is 1 word below exitSP. michael@0: returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); michael@0: #else michael@0: // For calls to Ion/C++ on ARM, the *caller* pushes the return address on michael@0: // the stack. For Ion, this is just part of the ABI. For C++, the return michael@0: // address is explicitly pushed before the call since we cannot expect the michael@0: // callee to immediately push lr. This means that exitSP points to the michael@0: // return address. michael@0: returnAddress_ = *(uint8_t**)sp_; michael@0: #endif michael@0: michael@0: settle(); michael@0: } michael@0: michael@0: struct GetCallSite michael@0: { michael@0: const AsmJSModule &module; michael@0: GetCallSite(const AsmJSModule &module) : module(module) {} michael@0: uint32_t operator[](size_t index) const { michael@0: return module.callSite(index).returnAddressOffset(); michael@0: } michael@0: }; michael@0: michael@0: void michael@0: AsmJSFrameIterator::popFrame() michael@0: { michael@0: // After adding stackDepth, sp points to the word before the return address, michael@0: // on both ARM and x86/x64. michael@0: sp_ += callsite_->stackDepth(); michael@0: returnAddress_ = *(uint8_t**)(sp_ - sizeof(void*)); michael@0: } michael@0: michael@0: void michael@0: AsmJSFrameIterator::settle() michael@0: { michael@0: while (true) { michael@0: uint32_t target = returnAddress_ - module_->codeBase(); michael@0: size_t lowerBound = 0; michael@0: size_t upperBound = module_->numCallSites(); michael@0: michael@0: size_t match; michael@0: if (!BinarySearch(GetCallSite(*module_), lowerBound, upperBound, target, &match)) { michael@0: callsite_ = nullptr; michael@0: return; michael@0: } michael@0: michael@0: callsite_ = &module_->callSite(match); michael@0: michael@0: if (callsite_->isExit()) { michael@0: popFrame(); michael@0: continue; michael@0: } michael@0: michael@0: if (callsite_->isEntry()) { michael@0: callsite_ = nullptr; michael@0: return; michael@0: } michael@0: michael@0: JS_ASSERT(callsite_->isNormal()); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: JSAtom * michael@0: AsmJSFrameIterator::functionDisplayAtom() const michael@0: { michael@0: JS_ASSERT(!done()); michael@0: return module_->functionName(callsite_->functionNameIndex()); michael@0: } michael@0: michael@0: unsigned michael@0: AsmJSFrameIterator::computeLine(uint32_t *column) const michael@0: { michael@0: JS_ASSERT(!done()); michael@0: if (column) michael@0: *column = callsite_->column(); michael@0: return callsite_->line(); michael@0: } michael@0: michael@0: static bool michael@0: CloneModule(JSContext *cx, MutableHandle moduleObj) michael@0: { michael@0: ScopedJSDeletePtr module; michael@0: if (!moduleObj->module().clone(cx, &module)) michael@0: return false; michael@0: michael@0: module->staticallyLink(cx); michael@0: michael@0: AsmJSModuleObject *newModuleObj = AsmJSModuleObject::create(cx, &module); michael@0: if (!newModuleObj) michael@0: return false; michael@0: michael@0: moduleObj.set(newModuleObj); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: LinkFail(JSContext *cx, const char *str) michael@0: { michael@0: JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, js_GetErrorMessage, michael@0: nullptr, JSMSG_USE_ASM_LINK_FAIL, str); michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: GetDataProperty(JSContext *cx, HandleValue objVal, HandlePropertyName field, MutableHandleValue v) michael@0: { michael@0: if (!objVal.isObject()) michael@0: return LinkFail(cx, "accessing property of non-object"); michael@0: michael@0: Rooted desc(cx); michael@0: RootedObject obj(cx, &objVal.toObject()); michael@0: RootedId id(cx, NameToId(field)); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc)) michael@0: return false; michael@0: michael@0: if (!desc.object()) michael@0: return LinkFail(cx, "property not present on object"); michael@0: michael@0: if (desc.hasGetterOrSetterObject()) michael@0: return LinkFail(cx, "property is not a data property"); michael@0: michael@0: v.set(desc.value()); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, michael@0: HandleValue importVal) michael@0: { michael@0: JS_ASSERT(global.which() == AsmJSModule::Global::Variable); michael@0: michael@0: void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); michael@0: michael@0: switch (global.varInitKind()) { michael@0: case AsmJSModule::Global::InitConstant: { michael@0: const Value &v = global.varInitConstant(); michael@0: switch (global.varInitCoercion()) { michael@0: case AsmJS_ToInt32: michael@0: *(int32_t *)datum = v.toInt32(); michael@0: break; michael@0: case AsmJS_ToNumber: michael@0: *(double *)datum = v.toDouble(); michael@0: break; michael@0: case AsmJS_FRound: michael@0: *(float *)datum = static_cast(v.toDouble()); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: case AsmJSModule::Global::InitImport: { michael@0: RootedPropertyName field(cx, global.varImportField()); michael@0: RootedValue v(cx); michael@0: if (!GetDataProperty(cx, importVal, field, &v)) michael@0: return false; michael@0: michael@0: switch (global.varInitCoercion()) { michael@0: case AsmJS_ToInt32: michael@0: if (!ToInt32(cx, v, (int32_t *)datum)) michael@0: return false; michael@0: break; michael@0: case AsmJS_ToNumber: michael@0: if (!ToNumber(cx, v, (double *)datum)) michael@0: return false; michael@0: break; michael@0: case AsmJS_FRound: michael@0: if (!RoundFloat32(cx, v, (float *)datum)) michael@0: return false; michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal, michael@0: AutoObjectVector *ffis) michael@0: { michael@0: RootedPropertyName field(cx, global.ffiField()); michael@0: RootedValue v(cx); michael@0: if (!GetDataProperty(cx, importVal, field, &v)) michael@0: return false; michael@0: michael@0: if (!v.isObject() || !v.toObject().is()) michael@0: return LinkFail(cx, "FFI imports must be functions"); michael@0: michael@0: (*ffis)[global.ffiIndex()] = &v.toObject().as(); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValidateArrayView(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal, michael@0: HandleValue bufferVal) michael@0: { michael@0: RootedPropertyName field(cx, global.viewName()); michael@0: RootedValue v(cx); michael@0: if (!GetDataProperty(cx, globalVal, field, &v)) michael@0: return false; michael@0: michael@0: if (!IsTypedArrayConstructor(v, global.viewType())) michael@0: return LinkFail(cx, "bad typed array constructor"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValidateMathBuiltinFunction(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) michael@0: { michael@0: RootedValue v(cx); michael@0: if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) michael@0: return false; michael@0: RootedPropertyName field(cx, global.mathName()); michael@0: if (!GetDataProperty(cx, v, field, &v)) michael@0: return false; michael@0: michael@0: Native native = nullptr; michael@0: switch (global.mathBuiltinFunction()) { michael@0: case AsmJSMathBuiltin_sin: native = math_sin; break; michael@0: case AsmJSMathBuiltin_cos: native = math_cos; break; michael@0: case AsmJSMathBuiltin_tan: native = math_tan; break; michael@0: case AsmJSMathBuiltin_asin: native = math_asin; break; michael@0: case AsmJSMathBuiltin_acos: native = math_acos; break; michael@0: case AsmJSMathBuiltin_atan: native = math_atan; break; michael@0: case AsmJSMathBuiltin_ceil: native = math_ceil; break; michael@0: case AsmJSMathBuiltin_floor: native = math_floor; break; michael@0: case AsmJSMathBuiltin_exp: native = math_exp; break; michael@0: case AsmJSMathBuiltin_log: native = math_log; break; michael@0: case AsmJSMathBuiltin_pow: native = js_math_pow; break; michael@0: case AsmJSMathBuiltin_sqrt: native = js_math_sqrt; break; michael@0: case AsmJSMathBuiltin_min: native = js_math_min; break; michael@0: case AsmJSMathBuiltin_max: native = js_math_max; break; michael@0: case AsmJSMathBuiltin_abs: native = js_math_abs; break; michael@0: case AsmJSMathBuiltin_atan2: native = math_atan2; break; michael@0: case AsmJSMathBuiltin_imul: native = math_imul; break; michael@0: case AsmJSMathBuiltin_fround: native = math_fround; break; michael@0: } michael@0: michael@0: if (!IsNativeFunction(v, native)) michael@0: return LinkFail(cx, "bad Math.* builtin function"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: ValidateConstant(JSContext *cx, AsmJSModule::Global &global, HandleValue globalVal) michael@0: { michael@0: RootedPropertyName field(cx, global.constantName()); michael@0: RootedValue v(cx, globalVal); michael@0: michael@0: if (global.constantKind() == AsmJSModule::Global::MathConstant) { michael@0: if (!GetDataProperty(cx, v, cx->names().Math, &v)) michael@0: return false; michael@0: } michael@0: michael@0: if (!GetDataProperty(cx, v, field, &v)) michael@0: return false; michael@0: if (!v.isNumber()) michael@0: return LinkFail(cx, "math / global constant value needs to be a number"); michael@0: michael@0: // NaN != NaN michael@0: if (IsNaN(global.constantValue())) { michael@0: if (!IsNaN(v.toNumber())) michael@0: return LinkFail(cx, "global constant value needs to be NaN"); michael@0: } else { michael@0: if (v.toNumber() != global.constantValue()) michael@0: return LinkFail(cx, "global constant value mismatch"); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: LinkModuleToHeap(JSContext *cx, AsmJSModule &module, Handle heap) michael@0: { michael@0: uint32_t heapLength = heap->byteLength(); michael@0: if (!IsValidAsmJSHeapLength(heapLength)) { michael@0: ScopedJSFreePtr msg( michael@0: JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " michael@0: "valid length is 0x%x", michael@0: heapLength, michael@0: RoundUpToNextValidAsmJSHeapLength(heapLength))); michael@0: return LinkFail(cx, msg.get()); michael@0: } michael@0: michael@0: // This check is sufficient without considering the size of the loaded datum because heap michael@0: // loads and stores start on an aligned boundary and the heap byteLength has larger alignment. michael@0: JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX); michael@0: if (heapLength < module.minHeapLength()) { michael@0: ScopedJSFreePtr msg( michael@0: JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the" michael@0: "largest constant heap access offset rounded up to the next valid " michael@0: "heap size).", michael@0: heapLength, michael@0: module.minHeapLength())); michael@0: return LinkFail(cx, msg.get()); michael@0: } michael@0: michael@0: if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) michael@0: return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); michael@0: michael@0: module.initHeap(heap, cx); michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) michael@0: { michael@0: module.setIsDynamicallyLinked(); michael@0: michael@0: RootedValue globalVal(cx); michael@0: if (args.length() > 0) michael@0: globalVal = args[0]; michael@0: michael@0: RootedValue importVal(cx); michael@0: if (args.length() > 1) michael@0: importVal = args[1]; michael@0: michael@0: RootedValue bufferVal(cx); michael@0: if (args.length() > 2) michael@0: bufferVal = args[2]; michael@0: michael@0: Rooted heap(cx); michael@0: if (module.hasArrayView()) { michael@0: if (!IsTypedArrayBuffer(bufferVal)) michael@0: return LinkFail(cx, "bad ArrayBuffer argument"); michael@0: michael@0: heap = &AsTypedArrayBuffer(bufferVal); michael@0: if (!LinkModuleToHeap(cx, module, heap)) michael@0: return false; michael@0: } michael@0: michael@0: AutoObjectVector ffis(cx); michael@0: if (!ffis.resize(module.numFFIs())) michael@0: return false; michael@0: michael@0: for (unsigned i = 0; i < module.numGlobals(); i++) { michael@0: AsmJSModule::Global &global = module.global(i); michael@0: switch (global.which()) { michael@0: case AsmJSModule::Global::Variable: michael@0: if (!ValidateGlobalVariable(cx, module, global, importVal)) michael@0: return false; michael@0: break; michael@0: case AsmJSModule::Global::FFI: michael@0: if (!ValidateFFI(cx, global, importVal, &ffis)) michael@0: return false; michael@0: break; michael@0: case AsmJSModule::Global::ArrayView: michael@0: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) michael@0: return false; michael@0: break; michael@0: case AsmJSModule::Global::MathBuiltinFunction: michael@0: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) michael@0: return false; michael@0: break; michael@0: case AsmJSModule::Global::Constant: michael@0: if (!ValidateConstant(cx, global, globalVal)) michael@0: return false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: for (unsigned i = 0; i < module.numExits(); i++) michael@0: module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static const unsigned ASM_MODULE_SLOT = 0; michael@0: static const unsigned ASM_EXPORT_INDEX_SLOT = 1; michael@0: michael@0: static unsigned michael@0: FunctionToExportedFunctionIndex(HandleFunction fun) michael@0: { michael@0: Value v = fun->getExtendedSlot(ASM_EXPORT_INDEX_SLOT); michael@0: return v.toInt32(); michael@0: } michael@0: michael@0: static const AsmJSModule::ExportedFunction & michael@0: FunctionToExportedFunction(HandleFunction fun, AsmJSModule &module) michael@0: { michael@0: unsigned funIndex = FunctionToExportedFunctionIndex(fun); michael@0: return module.exportedFunction(funIndex); michael@0: } michael@0: michael@0: static AsmJSModule & michael@0: FunctionToEnclosingModule(HandleFunction fun) michael@0: { michael@0: return fun->getExtendedSlot(ASM_MODULE_SLOT).toObject().as().module(); michael@0: } michael@0: michael@0: // The JSNative for the functions nested in an asm.js module. Calling this michael@0: // native will trampoline into generated code. michael@0: static bool michael@0: CallAsmJS(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs callArgs = CallArgsFromVp(argc, vp); michael@0: RootedFunction callee(cx, &callArgs.callee().as()); michael@0: michael@0: // An asm.js function stores, in its extended slots: michael@0: // - a pointer to the module from which it was returned michael@0: // - its index in the ordered list of exported functions michael@0: AsmJSModule &module = FunctionToEnclosingModule(callee); michael@0: michael@0: // An exported function points to the code as well as the exported michael@0: // function's signature, which implies the dynamic coercions performed on michael@0: // the arguments. michael@0: const AsmJSModule::ExportedFunction &func = FunctionToExportedFunction(callee, module); michael@0: michael@0: // The calling convention for an external call into asm.js is to pass an michael@0: // array of 8-byte values where each value contains either a coerced int32 michael@0: // (in the low word) or double value, with the coercions specified by the michael@0: // asm.js signature. The external entry point unpacks this array into the michael@0: // system-ABI-specified registers and stack memory and then calls into the michael@0: // internal entry point. The return value is stored in the first element of michael@0: // the array (which, therefore, must have length >= 1). michael@0: michael@0: js::Vector coercedArgs(cx); michael@0: if (!coercedArgs.resize(Max(1, func.numArgs()))) michael@0: return false; michael@0: michael@0: RootedValue v(cx); michael@0: for (unsigned i = 0; i < func.numArgs(); ++i) { michael@0: v = i < callArgs.length() ? callArgs[i] : UndefinedValue(); michael@0: switch (func.argCoercion(i)) { michael@0: case AsmJS_ToInt32: michael@0: if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i])) michael@0: return false; michael@0: break; michael@0: case AsmJS_ToNumber: michael@0: if (!ToNumber(cx, v, (double*)&coercedArgs[i])) michael@0: return false; michael@0: break; michael@0: case AsmJS_FRound: michael@0: if (!RoundFloat32(cx, v, (float *)&coercedArgs[i])) michael@0: return false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // An asm.js module is specialized to its heap's base address and length michael@0: // which is normally immutable except for the neuter operation that occurs michael@0: // when an ArrayBuffer is transfered. Throw an internal error if we're michael@0: // about to run with a neutered heap. michael@0: if (module.maybeHeapBufferObject() && module.maybeHeapBufferObject()->isNeutered()) { michael@0: js_ReportOverRecursed(cx); michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: // Push an AsmJSActivation to describe the asm.js frames we're about to michael@0: // push when running this module. Additionally, push a JitActivation so michael@0: // that the optimized asm.js-to-Ion FFI call path (which we want to be michael@0: // very fast) can avoid doing so. The JitActivation is marked as michael@0: // inactive so stack iteration will skip over it. michael@0: AsmJSActivation activation(cx, module); michael@0: JitActivation jitActivation(cx, /* firstFrameIsConstructing = */ false, /* active */ false); michael@0: michael@0: // Call the per-exported-function trampoline created by GenerateEntry. michael@0: AsmJSModule::CodePtr enter = module.entryTrampoline(func); michael@0: if (!CALL_GENERATED_ASMJS(enter, coercedArgs.begin(), module.globalData())) michael@0: return false; michael@0: } michael@0: michael@0: switch (func.returnType()) { michael@0: case AsmJSModule::Return_Void: michael@0: callArgs.rval().set(UndefinedValue()); michael@0: break; michael@0: case AsmJSModule::Return_Int32: michael@0: callArgs.rval().set(Int32Value(*(int32_t*)&coercedArgs[0])); michael@0: break; michael@0: case AsmJSModule::Return_Double: michael@0: callArgs.rval().set(NumberValue(*(double*)&coercedArgs[0])); michael@0: break; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static JSFunction * michael@0: NewExportedFunction(JSContext *cx, const AsmJSModule::ExportedFunction &func, michael@0: HandleObject moduleObj, unsigned exportIndex) michael@0: { michael@0: RootedPropertyName name(cx, func.name()); michael@0: JSFunction *fun = NewFunction(cx, NullPtr(), CallAsmJS, func.numArgs(), michael@0: JSFunction::NATIVE_FUN, cx->global(), name, michael@0: JSFunction::ExtendedFinalizeKind); michael@0: if (!fun) michael@0: return nullptr; michael@0: michael@0: fun->setExtendedSlot(ASM_MODULE_SLOT, ObjectValue(*moduleObj)); michael@0: fun->setExtendedSlot(ASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex)); michael@0: return fun; michael@0: } michael@0: michael@0: static bool michael@0: HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name) michael@0: { michael@0: if (cx->isExceptionPending()) michael@0: return false; michael@0: michael@0: uint32_t begin = module.offsetToEndOfUseAsm(); michael@0: uint32_t end = module.funcEndBeforeCurly(); michael@0: Rooted src(cx, module.scriptSource()->substring(cx, begin, end)); michael@0: if (!src) michael@0: return false; michael@0: michael@0: RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED, michael@0: cx->global(), name, JSFunction::FinalizeKind, michael@0: TenuredObject)); michael@0: if (!fun) michael@0: return false; michael@0: michael@0: AutoNameVector formals(cx); michael@0: formals.reserve(3); michael@0: if (module.globalArgumentName()) michael@0: formals.infallibleAppend(module.globalArgumentName()); michael@0: if (module.importArgumentName()) michael@0: formals.infallibleAppend(module.importArgumentName()); michael@0: if (module.bufferArgumentName()) michael@0: formals.infallibleAppend(module.bufferArgumentName()); michael@0: michael@0: CompileOptions options(cx); michael@0: options.setOriginPrincipals(module.scriptSource()->originPrincipals()) michael@0: .setCompileAndGo(false) michael@0: .setNoScriptRval(false); michael@0: michael@0: SourceBufferHolder srcBuf(src->chars(), end - begin, SourceBufferHolder::NoOwnership); michael@0: if (!frontend::CompileFunctionBody(cx, &fun, options, formals, srcBuf)) michael@0: return false; michael@0: michael@0: // Call the function we just recompiled. michael@0: args.setCallee(ObjectValue(*fun)); michael@0: return Invoke(cx, args); michael@0: } michael@0: michael@0: #ifdef MOZ_VTUNE michael@0: static bool michael@0: SendFunctionsToVTune(JSContext *cx, AsmJSModule &module) michael@0: { michael@0: uint8_t *base = module.codeBase(); michael@0: michael@0: for (unsigned i = 0; i < module.numProfiledFunctions(); i++) { michael@0: const AsmJSModule::ProfiledFunction &func = module.profiledFunction(i); michael@0: michael@0: uint8_t *start = base + func.pod.startCodeOffset; michael@0: uint8_t *end = base + func.pod.endCodeOffset; michael@0: JS_ASSERT(end >= start); michael@0: michael@0: unsigned method_id = iJIT_GetNewMethodID(); michael@0: if (method_id == 0) michael@0: return false; michael@0: michael@0: JSAutoByteString bytes; michael@0: const char *method_name = AtomToPrintableString(cx, func.name, &bytes); michael@0: if (!method_name) michael@0: return false; michael@0: michael@0: iJIT_Method_Load method; michael@0: method.method_id = method_id; michael@0: method.method_name = const_cast(method_name); michael@0: method.method_load_address = (void *)start; michael@0: method.method_size = unsigned(end - start); michael@0: method.line_number_size = 0; michael@0: method.line_number_table = nullptr; michael@0: method.class_id = 0; michael@0: method.class_file_name = nullptr; michael@0: method.source_file_name = nullptr; michael@0: michael@0: iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void *)&method); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: #ifdef JS_ION_PERF michael@0: static bool michael@0: SendFunctionsToPerf(JSContext *cx, AsmJSModule &module) michael@0: { michael@0: if (!PerfFuncEnabled()) michael@0: return true; michael@0: michael@0: uintptr_t base = (uintptr_t) module.codeBase(); michael@0: const char *filename = module.scriptSource()->filename(); michael@0: michael@0: for (unsigned i = 0; i < module.numProfiledFunctions(); i++) { michael@0: const AsmJSModule::ProfiledFunction &func = module.profiledFunction(i); michael@0: uintptr_t start = base + (unsigned long) func.pod.startCodeOffset; michael@0: uintptr_t end = base + (unsigned long) func.pod.endCodeOffset; michael@0: JS_ASSERT(end >= start); michael@0: size_t size = end - start; michael@0: michael@0: JSAutoByteString bytes; michael@0: const char *name = AtomToPrintableString(cx, func.name, &bytes); michael@0: if (!name) michael@0: return false; michael@0: michael@0: writePerfSpewerAsmJSFunctionMap(start, size, filename, func.pod.lineno, michael@0: func.pod.columnIndex, name); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: SendBlocksToPerf(JSContext *cx, AsmJSModule &module) michael@0: { michael@0: if (!PerfBlockEnabled()) michael@0: return true; michael@0: michael@0: unsigned long funcBaseAddress = (unsigned long) module.codeBase(); michael@0: const char *filename = module.scriptSource()->filename(); michael@0: michael@0: for (unsigned i = 0; i < module.numPerfBlocksFunctions(); i++) { michael@0: const AsmJSModule::ProfiledBlocksFunction &func = module.perfProfiledBlocksFunction(i); michael@0: michael@0: size_t size = func.pod.endCodeOffset - func.pod.startCodeOffset; michael@0: michael@0: JSAutoByteString bytes; michael@0: const char *name = AtomToPrintableString(cx, func.name, &bytes); michael@0: if (!name) michael@0: return false; michael@0: michael@0: writePerfSpewerAsmJSBlocksMap(funcBaseAddress, func.pod.startCodeOffset, michael@0: func.endInlineCodeOffset, size, filename, name, func.blocks); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: static bool michael@0: SendModuleToAttachedProfiler(JSContext *cx, AsmJSModule &module) michael@0: { michael@0: #if defined(MOZ_VTUNE) michael@0: if (IsVTuneProfilingActive() && !SendFunctionsToVTune(cx, module)) michael@0: return false; michael@0: #endif michael@0: michael@0: #if defined(JS_ION_PERF) michael@0: if (module.numExportedFunctions() > 0) { michael@0: size_t firstEntryCode = (size_t) module.entryTrampoline(module.exportedFunction(0)); michael@0: writePerfSpewerAsmJSEntriesAndExits(firstEntryCode, (size_t) module.globalData() - firstEntryCode); michael@0: } michael@0: if (!SendBlocksToPerf(cx, module)) michael@0: return false; michael@0: if (!SendFunctionsToPerf(cx, module)) michael@0: return false; michael@0: #endif michael@0: michael@0: return true; michael@0: } michael@0: michael@0: michael@0: static JSObject * michael@0: CreateExportObject(JSContext *cx, Handle moduleObj) michael@0: { michael@0: AsmJSModule &module = moduleObj->module(); michael@0: michael@0: if (module.numExportedFunctions() == 1) { michael@0: const AsmJSModule::ExportedFunction &func = module.exportedFunction(0); michael@0: if (!func.maybeFieldName()) michael@0: return NewExportedFunction(cx, func, moduleObj, 0); michael@0: } michael@0: michael@0: gc::AllocKind allocKind = gc::GetGCObjectKind(module.numExportedFunctions()); michael@0: RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, allocKind)); michael@0: if (!obj) michael@0: return nullptr; michael@0: michael@0: for (unsigned i = 0; i < module.numExportedFunctions(); i++) { michael@0: const AsmJSModule::ExportedFunction &func = module.exportedFunction(i); michael@0: michael@0: RootedFunction fun(cx, NewExportedFunction(cx, func, moduleObj, i)); michael@0: if (!fun) michael@0: return nullptr; michael@0: michael@0: JS_ASSERT(func.maybeFieldName() != nullptr); michael@0: RootedId id(cx, NameToId(func.maybeFieldName())); michael@0: RootedValue val(cx, ObjectValue(*fun)); michael@0: if (!DefineNativeProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE)) michael@0: return nullptr; michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: static const unsigned MODULE_FUN_SLOT = 0; michael@0: michael@0: static AsmJSModuleObject & michael@0: ModuleFunctionToModuleObject(JSFunction *fun) michael@0: { michael@0: return fun->getExtendedSlot(MODULE_FUN_SLOT).toObject().as(); michael@0: } michael@0: michael@0: // Implements the semantics of an asm.js module function that has been successfully validated. michael@0: static bool michael@0: LinkAsmJS(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: // The LinkAsmJS builtin (created by NewAsmJSModuleFunction) is an extended michael@0: // function and stores its module in an extended slot. michael@0: RootedFunction fun(cx, &args.callee().as()); michael@0: Rooted moduleObj(cx, &ModuleFunctionToModuleObject(fun)); michael@0: michael@0: // All ICache flushing of the module being linked has been inhibited under the michael@0: // assumption that the module is flushed after dynamic linking (when the last code michael@0: // mutation occurs). Thus, enter an AutoFlushICache context for the entire module michael@0: // now. The module range is set below. michael@0: AutoFlushICache afc("LinkAsmJS"); michael@0: michael@0: // When a module is linked, it is dynamically specialized to the given michael@0: // arguments (buffer, ffis). Thus, if the module is linked again (it is just michael@0: // a function so it can be called multiple times), we need to clone a new michael@0: // module. michael@0: if (moduleObj->module().isDynamicallyLinked()) { michael@0: if (!CloneModule(cx, &moduleObj)) michael@0: return false; michael@0: } else { michael@0: // CloneModule already calls setAutoFlushICacheRange internally before patching michael@0: // the cloned module, so avoid calling twice. michael@0: moduleObj->module().setAutoFlushICacheRange(); michael@0: } michael@0: michael@0: AsmJSModule &module = moduleObj->module(); michael@0: michael@0: // Link the module by performing the link-time validation checks in the michael@0: // asm.js spec and then patching the generated module to associate it with michael@0: // the given heap (ArrayBuffer) and a new global data segment (the closure michael@0: // state shared by the inner asm.js functions). michael@0: if (!DynamicallyLinkModule(cx, args, module)) { michael@0: // Linking failed, so reparse the entire asm.js module from scratch to michael@0: // get normal interpreted bytecode which we can simply Invoke. Very slow. michael@0: RootedPropertyName name(cx, fun->name()); michael@0: return HandleDynamicLinkFailure(cx, args, module, name); michael@0: } michael@0: michael@0: // Notify profilers so that asm.js generated code shows up with JS function michael@0: // names and lines in native (i.e., not SPS) profilers. michael@0: if (!SendModuleToAttachedProfiler(cx, module)) michael@0: return false; michael@0: michael@0: // Link-time validation succeeded, so wrap all the exported functions with michael@0: // CallAsmJS builtins that trampoline into the generated code. michael@0: JSObject *obj = CreateExportObject(cx, moduleObj); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: args.rval().set(ObjectValue(*obj)); michael@0: return true; michael@0: } michael@0: michael@0: JSFunction * michael@0: js::NewAsmJSModuleFunction(ExclusiveContext *cx, JSFunction *origFun, HandleObject moduleObj) michael@0: { michael@0: RootedPropertyName name(cx, origFun->name()); michael@0: michael@0: JSFunction::Flags flags = origFun->isLambda() ? JSFunction::NATIVE_LAMBDA_FUN michael@0: : JSFunction::NATIVE_FUN; michael@0: JSFunction *moduleFun = NewFunction(cx, NullPtr(), LinkAsmJS, origFun->nargs(), michael@0: flags, NullPtr(), name, michael@0: JSFunction::ExtendedFinalizeKind, TenuredObject); michael@0: if (!moduleFun) michael@0: return nullptr; michael@0: michael@0: moduleFun->setExtendedSlot(MODULE_FUN_SLOT, ObjectValue(*moduleObj)); michael@0: return moduleFun; michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSModuleNative(js::Native native) michael@0: { michael@0: return native == LinkAsmJS; michael@0: } michael@0: michael@0: static bool michael@0: IsMaybeWrappedNativeFunction(const Value &v, Native native, JSFunction **fun = nullptr) michael@0: { michael@0: if (!v.isObject()) michael@0: return false; michael@0: michael@0: JSObject *obj = CheckedUnwrap(&v.toObject()); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: if (!obj->is()) michael@0: return false; michael@0: michael@0: if (fun) michael@0: *fun = &obj->as(); michael@0: michael@0: return obj->as().maybeNative() == native; michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSModule(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args.get(0), LinkAsmJS); michael@0: args.rval().set(BooleanValue(rval)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSModule(HandleFunction fun) michael@0: { michael@0: return fun->isNative() && fun->maybeNative() == LinkAsmJS; michael@0: } michael@0: michael@0: JSString * michael@0: js::AsmJSModuleToString(JSContext *cx, HandleFunction fun, bool addParenToLambda) michael@0: { michael@0: AsmJSModule &module = ModuleFunctionToModuleObject(fun).module(); michael@0: michael@0: uint32_t begin = module.funcStart(); michael@0: uint32_t end = module.funcEndAfterCurly(); michael@0: ScriptSource *source = module.scriptSource(); michael@0: StringBuffer out(cx); michael@0: michael@0: // Whether the function has been created with a Function ctor michael@0: bool funCtor = begin == 0 && end == source->length() && source->argumentsNotIncluded(); michael@0: michael@0: if (addParenToLambda && fun->isLambda() && !out.append("(")) michael@0: return nullptr; michael@0: michael@0: if (!out.append("function ")) michael@0: return nullptr; michael@0: michael@0: if (fun->atom() && !out.append(fun->atom())) michael@0: return nullptr; michael@0: michael@0: if (funCtor) { michael@0: // Functions created with the function constructor don't have arguments in their source. michael@0: if (!out.append("(")) michael@0: return nullptr; michael@0: michael@0: if (PropertyName *argName = module.globalArgumentName()) { michael@0: if (!out.append(argName->chars(), argName->length())) michael@0: return nullptr; michael@0: } michael@0: if (PropertyName *argName = module.importArgumentName()) { michael@0: if (!out.append(", ") || !out.append(argName->chars(), argName->length())) michael@0: return nullptr; michael@0: } michael@0: if (PropertyName *argName = module.bufferArgumentName()) { michael@0: if (!out.append(", ") || !out.append(argName->chars(), argName->length())) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!out.append(") {\n")) michael@0: return nullptr; michael@0: } michael@0: michael@0: Rooted src(cx, source->substring(cx, begin, end)); michael@0: if (!src) michael@0: return nullptr; michael@0: michael@0: if (module.strict()) { michael@0: // We need to add "use strict" in the body right after the opening michael@0: // brace. michael@0: size_t bodyStart = 0, bodyEnd; michael@0: michael@0: // No need to test for functions created with the Function ctor as michael@0: // these doesn't implicitly inherit the "use strict" context. Strict mode is michael@0: // enabled for functions created with the Function ctor only if they begin with michael@0: // the "use strict" directive, but these functions won't validate as asm.js michael@0: // modules. michael@0: michael@0: ConstTwoByteChars chars(src->chars(), src->length()); michael@0: if (!FindBody(cx, fun, chars, src->length(), &bodyStart, &bodyEnd)) michael@0: return nullptr; michael@0: michael@0: if (!out.append(chars, bodyStart) || michael@0: !out.append("\n\"use strict\";\n") || michael@0: !out.append(chars + bodyStart, src->length() - bodyStart)) michael@0: { michael@0: return nullptr; michael@0: } michael@0: } else { michael@0: if (!out.append(src->chars(), src->length())) michael@0: return nullptr; michael@0: } michael@0: michael@0: if (funCtor && !out.append("\n}")) michael@0: return nullptr; michael@0: michael@0: if (addParenToLambda && fun->isLambda() && !out.append(")")) michael@0: return nullptr; michael@0: michael@0: return out.finishString(); michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSModuleLoadedFromCache(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: michael@0: JSFunction *fun; michael@0: if (!args.hasDefined(0) || !IsMaybeWrappedNativeFunction(args[0], LinkAsmJS, &fun)) { michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_USE_ASM_TYPE_FAIL, michael@0: "argument passed to isAsmJSModuleLoadedFromCache is not a " michael@0: "validated asm.js module"); michael@0: return false; michael@0: } michael@0: michael@0: bool loadedFromCache = ModuleFunctionToModuleObject(fun).module().loadedFromCache(); michael@0: michael@0: args.rval().set(BooleanValue(loadedFromCache)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSFunction(JSContext *cx, unsigned argc, Value *vp) michael@0: { michael@0: CallArgs args = CallArgsFromVp(argc, vp); michael@0: bool rval = args.hasDefined(0) && IsMaybeWrappedNativeFunction(args[0], CallAsmJS); michael@0: args.rval().set(BooleanValue(rval)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: js::IsAsmJSFunction(HandleFunction fun) michael@0: { michael@0: return fun->isNative() && fun->maybeNative() == CallAsmJS; michael@0: } michael@0: michael@0: JSString * michael@0: js::AsmJSFunctionToString(JSContext *cx, HandleFunction fun) michael@0: { michael@0: AsmJSModule &module = FunctionToEnclosingModule(fun); michael@0: const AsmJSModule::ExportedFunction &f = FunctionToExportedFunction(fun, module); michael@0: uint32_t begin = module.funcStart() + f.startOffsetInModule(); michael@0: uint32_t end = module.funcStart() + f.endOffsetInModule(); michael@0: michael@0: ScriptSource *source = module.scriptSource(); michael@0: StringBuffer out(cx); michael@0: michael@0: // asm.js functions cannot have been created with a Function constructor michael@0: // as they belong within a module. michael@0: JS_ASSERT(!(begin == 0 && end == source->length() && source->argumentsNotIncluded())); michael@0: michael@0: if (!out.append("function ")) michael@0: return nullptr; michael@0: michael@0: Rooted src(cx, source->substring(cx, begin, end)); michael@0: if (!src) michael@0: return nullptr; michael@0: michael@0: if (!out.append(src->chars(), src->length())) michael@0: return nullptr; michael@0: michael@0: return out.finishString(); michael@0: }