/* -*- 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 moduleObj) { ScopedJSDeletePtr 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, 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 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(v) || IsVectorObject(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() && HasObjectValueOf(&v.toObject(), cx) && ClassMethodIsNative(cx, &v.toObject().as(), &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(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(cx, v, &simdConstant)) return false; memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize); break; case AsmJS_ToFloat32x4: if (!ToSimdConstant(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()) return LinkFail(cx, "FFI imports must be functions"); (*ffis)[global.ffiIndex()].set(&v.toObject().as()); 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()); 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()) return LinkFail(cx, "bad SIMD type"); if (AsmJSSimdTypeToTypeDescrType(type) != simdDesc->as().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 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 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 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 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()) { Rooted abheap(cx, &heap->as()); 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 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(); 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 newBuffer(cx, &bufferArg.toObject().as()); 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().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()); 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, cx->runtime()->spsProfiler.enabled()); // 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 coercedArgs(cx); if (!coercedArgs.resize(Max(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(cx, v, &simd)) return false; memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize); break; } case AsmJS_ToFloat32x4: { SimdConstant simd; if (!ToSimdConstant(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_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(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(cx, (int32_t*)&coercedArgs[0]); if (!simdObj) return false; callArgs.rval().set(ObjectValue(*simdObj)); break; case AsmJSModule::Return_Float32x4: simdObj = CreateSimd(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 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(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 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(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(); } // 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()); Rooted 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()) return false; if (fun) *fun = &obj->as(); return obj->as().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 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 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); // 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 src(cx, source->substring(cx, nameEnd, end)); if (!AppendUseStrictSource(cx, fun, src, out)) return nullptr; } else { Rooted src(cx, source->substring(cx, begin, end)); if (!src) return nullptr; if (!out.append(src)) return nullptr; } return out.finishString(); }