static bool LinkModuleToHeap(JSContext *cx, AsmJSModule &module, Handle<ArrayBufferObject*> heap) { if (!IsValidAsmJSHeapLength(heap->byteLength())) { ScopedJSFreePtr<char> msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " "valid length is 0x%x", heap->byteLength(), RoundUpToNextValidAsmJSHeapLength(heap->byteLength()))); 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. JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX); if (heap->byteLength() < module.minHeapLength()) { ScopedJSFreePtr<char> msg( JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the" "largest constant heap access offset rounded up to the next valid " "heap size).", heap->byteLength(), module.minHeapLength())); return LinkFail(cx, msg.get()); } if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); module.initHeap(heap, cx); return true; }
AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) : Activation(cx, AsmJS), module_(module), entrySP_(nullptr), profiler_(nullptr), resumePC_(nullptr), fp_(nullptr), exitReason_(AsmJSExit::None) { (void) entrySP_; // squelch GCC warning // NB: this is a hack and can be removed once Ion switches over to // JS::ProfilingFrameIterator. if (cx->runtime()->spsProfiler.enabled()) { profiler_ = &cx->runtime()->spsProfiler; profiler_->enterAsmJS("asm.js code :0", this); } prevAsmJSForModule_ = module.activation(); module.activation() = this; prevAsmJS_ = cx->mainThread().asmJSActivationStack_; cx->mainThread().asmJSActivationStack_ = this; // Now that the AsmJSActivation is fully initialized, make it visible to // asynchronous profiling. registerProfiling(); }
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.numPerfFunctions(); i++) { const AsmJSModule::ProfiledFunction &func = module.perfProfiledFunction(i); uintptr_t start = base + (unsigned long) func.startCodeOffset; uintptr_t end = base + (unsigned long) func.endCodeOffset; JS_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.lineno, func.columnIndex, name); } return true; }
AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) : Activation(cx, AsmJS), module_(module), errorRejoinSP_(nullptr), profiler_(nullptr), resumePC_(nullptr), fp_(nullptr), exitReason_(AsmJSExit::None) { if (cx->runtime()->spsProfiler.enabled()) { // Use a profiler string that matches jsMatch regex in // browser/devtools/profiler/cleopatra/js/parserWorker.js. // (For now use a single static string to avoid further slowing down // calls into asm.js.) profiler_ = &cx->runtime()->spsProfiler; profiler_->enterAsmJS("asm.js code :0", this); } prevAsmJSForModule_ = module.activation(); module.activation() = this; prevAsmJS_ = cx->mainThread().asmJSActivationStack_; JSRuntime::AutoLockForInterrupt lock(cx->runtime()); cx->mainThread().asmJSActivationStack_ = this; (void) errorRejoinSP_; // squelch GCC warning }
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.endCodeOffset - func.startCodeOffset; JSAutoByteString bytes; const char *name = AtomToPrintableString(cx, func.name, &bytes); if (!name) return false; writePerfSpewerAsmJSBlocksMap(funcBaseAddress, func.startCodeOffset, func.endInlineCodeOffset, size, filename, name, func.blocks); } return true; }
static bool SendBlocksToPerf(JSContext *cx, AsmJSModule &module) { if (!PerfBlockEnabled()) return true; AsmJSPerfSpewer spewer; unsigned long funcBaseAddress = (unsigned long) module.functionCode(); const AsmJSModule::PostLinkFailureInfo &info = module.postLinkFailureInfo(); const char *filename = const_cast<char *>(info.scriptSource->filename()); for (unsigned i = 0; i < module.numPerfBlocksFunctions(); i++) { const AsmJSModule::ProfiledBlocksFunction &func = module.perfProfiledBlocksFunction(i); unsigned long size = (unsigned long)func.endCodeOffset - (unsigned long)func.startCodeOffset; JSAutoByteString bytes; const char *method_name = AtomToPrintableString(cx, func.name, &bytes); if (!method_name) return false; spewer.writeBlocksMap(funcBaseAddress, func.startCodeOffset, size, filename, method_name, func.blocks); } return true; }
static bool SendFunctionsToPerf(JSContext *cx, AsmJSModule &module) { if (!PerfFuncEnabled()) return true; AsmJSPerfSpewer perfSpewer; unsigned long base = (unsigned long) module.functionCode(); const AsmJSModule::PostLinkFailureInfo &info = module.postLinkFailureInfo(); const char *filename = const_cast<char *>(info.scriptSource->filename()); for (unsigned i = 0; i < module.numPerfFunctions(); i++) { const AsmJSModule::ProfiledFunction &func = module.perfProfiledFunction(i); unsigned long start = base + (unsigned long) func.startCodeOffset; unsigned long end = base + (unsigned long) func.endCodeOffset; JS_ASSERT(end >= start); unsigned long size = (end - start); JSAutoByteString bytes; const char *method_name = AtomToPrintableString(cx, func.name, &bytes); if (!method_name) return false; unsigned lineno = func.lineno; unsigned columnIndex = func.columnIndex; perfSpewer.writeFunctionMap(start, size, filename, lineno, columnIndex, method_name); } return true; }
static bool HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name) { if (cx->isExceptionPending()) return false; uint32_t begin = module.charsBegin(); uint32_t end = module.charsEnd(); Rooted<JSStableString*> src(cx, module.scriptSource()->substring(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); formals.reserve(3); 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.setPrincipals(cx->compartment()->principals) .setOriginPrincipals(module.scriptSource()->originPrincipals()) .setCompileAndGo(false) .setNoScriptRval(false); if (!frontend::CompileFunctionBody(cx, &fun, options, formals, src->chars().get(), end - begin)) return false; // Call the function we just recompiled. unsigned argc = args.length(); InvokeArgs args2(cx); if (!args2.init(argc)) return false; args2.setCallee(ObjectValue(*fun)); args2.setThis(args.thisv()); for (unsigned i = 0; i < argc; i++) args2[i].set(args[i]); if (!Invoke(cx, args2)) return false; args.rval().set(args2.rval()); return true; }
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 (!SendFunctionsToPerf(cx, module)) return false; #endif 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 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.entryTrampoline(module.exportedFunction(0)); writePerfSpewerAsmJSEntriesAndExits(firstEntryCode, (size_t) module.globalData() - firstEntryCode); } if (!SendBlocksToPerf(cx, module)) return false; if (!SendFunctionsToPerf(cx, module)) return false; #endif return true; }
static bool HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name) { if (cx->isExceptionPending()) return false; const AsmJSModule::PostLinkFailureInfo &info = module.postLinkFailureInfo(); uint32_t length = info.bufEnd_ - info.bufStart_; Rooted<JSFlatString*> src(cx, info.scriptSource_->substring(cx, info.bufStart_, info.bufEnd_)); const jschar *chars = src->chars(); RootedFunction fun(cx, NewFunction(cx, NullPtr(), NULL, 0, JSFunction::INTERPRETED, cx->global(), name)); if (!fun) return false; AutoNameVector formals(cx); formals.reserve(3); if (module.globalArgumentName()) formals.infallibleAppend(module.globalArgumentName()); if (module.importArgumentName()) formals.infallibleAppend(module.importArgumentName()); if (module.bufferArgumentName()) formals.infallibleAppend(module.bufferArgumentName()); if (!frontend::CompileFunctionBody(cx, &fun, info.options_, formals, chars, length, /* isAsmJSRecompile = */ true)) return false; // Call the function we just recompiled. unsigned argc = args.length(); JS_ASSERT(argc <= 3); InvokeArgsGuard args2; if (!cx->stack.pushInvokeArgs(cx, args.length(), &args2)) return false; args2.setCallee(ObjectValue(*fun)); args2.setThis(args.thisv()); if (argc > 0) args2[0] = args[0]; if (argc > 1) args2[1] = args[1]; if (argc > 2) args2[2] = args[2]; if (!Invoke(cx, args2)) return false; args.rval().set(args2.rval()); return true; }
static bool ChangeHeap(JSContext *cx, AsmJSModule &module, CallArgs args) { HandleValue bufferArg = args.get(0); if (!IsArrayBuffer(bufferArg)) { ReportIncompatible(cx, args); return false; } Rooted<ArrayBufferObject*> newBuffer(cx, &bufferArg.toObject().as<ArrayBufferObject>()); uint32_t heapLength = newBuffer->byteLength(); if (heapLength & module.heapLengthMask() || heapLength < module.minHeapLength() || heapLength > module.maxHeapLength()) { args.rval().set(BooleanValue(false)); return true; } if (!module.hasArrayView()) { args.rval().set(BooleanValue(true)); return true; } MOZ_ASSERT(IsValidAsmJSHeapLength(heapLength)); MOZ_ASSERT(!IsDeprecatedAsmJSHeapLength(heapLength)); if (!ArrayBufferObject::prepareForAsmJS(cx, newBuffer, module.usesSignalHandlersForOOB())) return false; args.rval().set(BooleanValue(module.changeHeap(newBuffer, cx))); return true; }
static inline void AssertMatchesCallSite(const AsmJSModule &module, const AsmJSModule::CodeRange *calleeCodeRange, void *callerPC, void *callerFP, void *fp) { #ifdef DEBUG const AsmJSModule::CodeRange *callerCodeRange = module.lookupCodeRange(callerPC); JS_ASSERT(callerCodeRange); if (callerCodeRange->isEntry()) { JS_ASSERT(callerFP == nullptr); return; } const CallSite *callsite = module.lookupCallSite(callerPC); if (calleeCodeRange->isThunk()) { JS_ASSERT(!callsite); JS_ASSERT(callerCodeRange->isFunction()); } else { JS_ASSERT(callsite); JS_ASSERT(callerFP == (uint8_t*)fp + callsite->stackDepth()); } #endif }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { JS_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const Value &v = global.varInitConstant(); switch (global.varInitCoercion()) { case AsmJS_ToInt32: *(int32_t *)datum = v.toInt32(); break; case AsmJS_ToNumber: *(double *)datum = v.toDouble(); break; case AsmJS_FRound: *(float *)datum = static_cast<float>(v.toDouble()); break; } break; } case AsmJSModule::Global::InitImport: { RootedPropertyName field(cx, global.varImportField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; 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; } break; } } return true; }
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.startCodeOffset; uint8_t *end = base + func.endCodeOffset; JS_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; }
static bool LinkModuleToHeap(JSContext *cx, AsmJSModule &module, Handle<ArrayBufferObject*> heap) { uint32_t heapLength = heap->byteLength(); if (IsDeprecatedAsmJSHeapLength(heapLength)) { LinkFail(cx, "ArrayBuffer byteLengths smaller than 64KB are deprecated and " "will cause a link-time failure in the future"); // The goal of deprecation is to give apps some time before linking // fails. However, if warnings-as-errors is turned on (which happens as // part of asm.js testing) an exception may be raised. if (cx->isExceptionPending()) return false; } if (!IsValidAsmJSHeapLength(heapLength)) { ScopedJSFreePtr<char> msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " "valid length is 0x%x", heapLength, RoundUpToNextValidAsmJSHeapLength(heapLength))); return LinkFail(cx, msg.get()); } // This check is sufficient without considering the size of the loaded datum because heap // loads and stores start on an aligned boundary and the heap byteLength has larger alignment. JS_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 (which is the " "largest constant heap access offset rounded up to the next valid " "heap size).", heapLength, module.minHeapLength())); 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 (!ArrayBufferObject::prepareForAsmJS(cx, heap, module.usesSignalHandlersForOOB())) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); module.initHeap(heap, cx); return true; }
static bool DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { if (module.isLinked()) return LinkFail(cx, "As a temporary limitation, modules cannot be linked more than " "once. This limitation should be removed in a future release. To " "work around this, compile a second module (e.g., using the " "Function constructor)."); RootedValue globalVal(cx, UndefinedValue()); if (args.length() > 0) globalVal = args[0]; RootedValue importVal(cx, UndefinedValue()); if (args.length() > 1) importVal = args[1]; RootedValue bufferVal(cx, UndefinedValue()); if (args.length() > 2) bufferVal = args[2]; Rooted<ArrayBufferObject*> heap(cx); if (module.hasArrayView()) { if (!IsTypedArrayBuffer(bufferVal)) return LinkFail(cx, "bad ArrayBuffer argument"); heap = &bufferVal.toObject().as<ArrayBufferObject>(); if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity) return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096"); if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); module.patchHeapAccesses(heap, cx); } 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: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) return false; break; case AsmJSModule::Global::MathBuiltin: if (!ValidateMathBuiltin(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateGlobalConstant(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); module.setIsLinked(heap); return true; }
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 DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { if (module.isLinked()) return LinkFail(cx, "As a temporary limitation, modules cannot be linked more than " "once. This limitation should be removed in a future release. To " "work around this, compile a second module (e.g., using the " "Function constructor)."); RootedValue globalVal(cx, UndefinedValue()); if (args.length() > 0) globalVal = args[0]; RootedValue importVal(cx, UndefinedValue()); if (args.length() > 1) importVal = args[1]; RootedValue bufferVal(cx, UndefinedValue()); if (args.length() > 2) bufferVal = args[2]; Rooted<ArrayBufferObject*> heap(cx); if (module.hasArrayView()) { if (!IsTypedArrayBuffer(bufferVal)) return LinkFail(cx, "bad ArrayBuffer argument"); heap = &bufferVal.toObject().as<ArrayBufferObject>(); if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity) return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096"); if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); #if defined(JS_CPU_X86) void *heapOffset = (void*)heap->dataPointer(); void *heapLength = (void*)heap->byteLength(); uint8_t *code = module.functionCode(); for (unsigned i = 0; i < module.numHeapAccesses(); i++) { const AsmJSHeapAccess &access = module.heapAccess(i); JSC::X86Assembler::setPointer(access.patchLengthAt(code), heapLength); JSC::X86Assembler::setPointer(access.patchOffsetAt(code), heapOffset); } #elif defined(JS_CPU_ARM) // Now the length of the array is know, patch all of the bounds check sites // with the new length. jit::IonContext ic(cx, NULL); module.patchBoundsChecks(heap->byteLength()); #endif } 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: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) return false; break; case AsmJSModule::Global::MathBuiltin: if (!ValidateMathBuiltin(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateGlobalConstant(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); module.setIsLinked(heap); return true; }
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, /* enclosingScope = */ nullptr)) return false; // Call the function we just recompiled. args.setCallee(ObjectValue(*fun)); return Invoke(cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT); }
static bool DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { module.setIsDynamicallyLinked(); RootedValue globalVal(cx); if (args.length() > 0) globalVal = args[0]; RootedValue importVal(cx); if (args.length() > 1) importVal = args[1]; RootedValue bufferVal(cx); if (args.length() > 2) bufferVal = args[2]; Rooted<ArrayBufferObject*> heap(cx); if (module.hasArrayView()) { if (!IsTypedArrayBuffer(bufferVal)) return LinkFail(cx, "bad ArrayBuffer argument"); heap = &AsTypedArrayBuffer(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: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) return false; break; case AsmJSModule::Global::MathBuiltinFunction: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateConstant(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); return true; }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { MOZ_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarToGlobalDatum(global); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const AsmJSNumLit &lit = global.varInitNumLit(); switch (lit.which()) { case AsmJSNumLit::Fixnum: case AsmJSNumLit::NegativeInt: case AsmJSNumLit::BigUnsigned: *(int32_t *)datum = lit.scalarValue().toInt32(); break; case AsmJSNumLit::Double: *(double *)datum = lit.scalarValue().toDouble(); break; case AsmJSNumLit::Float: *(float *)datum = static_cast<float>(lit.scalarValue().toDouble()); break; case AsmJSNumLit::Int32x4: memcpy(datum, lit.simdValue().asInt32x4(), Simd128DataSize); break; case AsmJSNumLit::Float32x4: memcpy(datum, lit.simdValue().asFloat32x4(), Simd128DataSize); break; case AsmJSNumLit::OutOfRangeInt: MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("OutOfRangeInt isn't valid in the first place"); } break; } case AsmJSModule::Global::InitImport: { RootedPropertyName field(cx, global.varImportField()); RootedValue v(cx); if (!GetDataProperty(cx, importVal, field, &v)) return false; if (!v.isPrimitive() && !HasPureCoercion(cx, v)) return LinkFail(cx, "Imported values must be primitives"); SimdConstant simdConstant; switch (global.varInitCoercion()) { case AsmJS_ToInt32: if (!ToInt32(cx, v, (int32_t *)datum)) return false; break; case AsmJS_ToNumber: if (!ToNumber(cx, v, (double *)datum)) return false; break; case AsmJS_FRound: if (!RoundFloat32(cx, v, (float *)datum)) return false; break; case AsmJS_ToInt32x4: if (!ToSimdConstant<Int32x4>(cx, v, &simdConstant)) return false; memcpy(datum, simdConstant.asInt32x4(), Simd128DataSize); break; case AsmJS_ToFloat32x4: if (!ToSimdConstant<Float32x4>(cx, v, &simdConstant)) return false; memcpy(datum, simdConstant.asFloat32x4(), Simd128DataSize); break; } break; } } return true; }
static bool ValidateGlobalVariable(JSContext *cx, const AsmJSModule &module, AsmJSModule::Global &global, HandleValue importVal) { JS_ASSERT(global.which() == AsmJSModule::Global::Variable); void *datum = module.globalVarIndexToGlobalDatum(global.varIndex()); switch (global.varInitKind()) { case AsmJSModule::Global::InitConstant: { const AsmJSNumLit &lit = global.varInitNumLit(); const Value &v = lit.value(); switch (lit.which()) { case AsmJSNumLit::Fixnum: case AsmJSNumLit::NegativeInt: case AsmJSNumLit::BigUnsigned: *(int32_t *)datum = v.toInt32(); break; case AsmJSNumLit::Double: *(double *)datum = v.toDouble(); break; case AsmJSNumLit::Float: *(float *)datum = static_cast<float>(v.toDouble()); 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()) { // Ideally, we'd reject all 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(), &JSFunction::class_, toString, fun_toString)) { return LinkFail(cx, "Imported values must be primitives"); } } 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; } break; } } return true; }
static bool HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name) { if (cx->isExceptionPending()) return false; uint32_t begin = module.srcBodyStart(); // starts right after 'use asm' uint32_t end = module.srcEndBeforeCurly(); Rooted<JSFlatString*> src(cx, module.scriptSource()->substringDontDeflate(cx, begin, end)); if (!src) return false; RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED, cx->global(), name, JSFunction::FinalizeKind, TenuredObject)); if (!fun) return false; AutoNameVector formals(cx); formals.reserve(3); 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.setOriginPrincipals(module.scriptSource()->originPrincipals()) .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 jschar *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); }
static bool DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { module.setIsDynamicallyLinked(cx->runtime()); HandleValue globalVal = args.get(0); HandleValue importVal = args.get(1); HandleValue bufferVal = args.get(2); Rooted<ArrayBufferObjectMaybeShared *> heap(cx); if (module.hasArrayView()) { if (module.isSharedView() && !IsSharedArrayBuffer(bufferVal)) return LinkFail(cx, "shared views can only be constructed onto SharedArrayBuffer"); if (!module.isSharedView() && !IsArrayBuffer(bufferVal)) return LinkFail(cx, "unshared views can only be constructed onto ArrayBuffer"); heap = &AsAnyArrayBuffer(bufferVal); if (!LinkModuleToHeap(cx, module, heap)) return false; } AutoObjectVector ffis(cx); if (!ffis.resize(module.numFFIs())) return false; for (unsigned i = 0; i < module.numGlobals(); i++) { AsmJSModule::Global &global = module.global(i); switch (global.which()) { case AsmJSModule::Global::Variable: if (!ValidateGlobalVariable(cx, module, global, importVal)) return false; break; case AsmJSModule::Global::FFI: if (!ValidateFFI(cx, global, importVal, &ffis)) return false; break; case AsmJSModule::Global::ArrayView: case AsmJSModule::Global::SharedArrayView: case AsmJSModule::Global::ArrayViewCtor: if (!ValidateArrayView(cx, global, globalVal, module.hasArrayView() && module.isSharedView())) return false; break; case AsmJSModule::Global::ByteLength: if (!ValidateByteLength(cx, globalVal)) return false; break; case AsmJSModule::Global::MathBuiltinFunction: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSModule::Global::AtomicsBuiltinFunction: if (!ValidateAtomicsBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateConstant(cx, global, globalVal)) return false; break; case AsmJSModule::Global::SimdCtor: if (!ValidateSimdType(cx, global, globalVal)) return false; break; case AsmJSModule::Global::SimdOperation: if (!ValidateSimdOperation(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); module.initGlobalNaN(); return true; }
static bool DynamicallyLinkModule(JSContext *cx, CallArgs args, AsmJSModule &module) { if (module.isLinked()) return LinkFail(cx, "As a temporary limitation, modules cannot be linked more than " "once. This limitation should be removed in a future release. To " "work around this, compile a second module (e.g., using the " "Function constructor)."); module.setIsLinked(); RootedValue globalVal(cx, UndefinedValue()); if (args.length() > 0) globalVal = args[0]; RootedValue importVal(cx, UndefinedValue()); if (args.length() > 1) importVal = args[1]; RootedValue bufferVal(cx, UndefinedValue()); if (args.length() > 2) bufferVal = args[2]; Rooted<ArrayBufferObject*> heap(cx); if (module.hasArrayView()) { if (!IsTypedArrayBuffer(bufferVal)) return LinkFail(cx, "bad ArrayBuffer argument"); heap = &bufferVal.toObject().as<ArrayBufferObject>(); if (!IsValidAsmJSHeapLength(heap->byteLength())) { return LinkFail(cx, JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next valid length is 0x%x", heap->byteLength(), RoundUpToNextValidAsmJSHeapLength(heap->byteLength()))); } // 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. JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX); if (heap->byteLength() < module.minHeapLength()) { return LinkFail(cx, JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (which is the largest constant heap access offset rounded up to the next valid heap size).", heap->byteLength(), module.minHeapLength())); } if (!ArrayBufferObject::prepareForAsmJS(cx, heap)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); module.initHeap(heap, cx); } 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: if (!ValidateArrayView(cx, global, globalVal, bufferVal)) return false; break; case AsmJSModule::Global::MathBuiltin: if (!ValidateMathBuiltin(cx, global, globalVal)) return false; break; case AsmJSModule::Global::Constant: if (!ValidateGlobalConstant(cx, global, globalVal)) return false; break; } } for (unsigned i = 0; i < module.numExits(); i++) module.exitIndexToGlobalDatum(i).fun = &ffis[module.exit(i).ffiIndex()]->as<JSFunction>(); return true; }