author | Luke Wagner <luke@mozilla.com> |
Tue, 11 Nov 2014 08:36:52 -0600 | |
changeset 215515 | 7db30249d1d86f01440b619f78d33e99d1e3fd9b |
parent 215514 | 97408585e41c165f6e5d0fdc0eeedda92cd237b4 |
child 215516 | 90d067dbe461e1cfd1ed9002d973d2ce121da961 |
push id | 27818 |
push user | ryanvm@gmail.com |
push date | Thu, 13 Nov 2014 20:19:09 +0000 |
treeherder | mozilla-central@292ed84594c1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bhackett |
bugs | 1091912 |
milestone | 36.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -49,46 +49,16 @@ using namespace js; using namespace jit; using namespace frontend; using mozilla::BinarySearch; using mozilla::PodCopy; using mozilla::PodEqual; using mozilla::Compression::LZ4; using mozilla::Swap; -// At any time, the executable code of an asm.js module can be protected (as -// part of RequestInterruptForAsmJSCode). When we touch the executable outside -// of executing it (which the AsmJSFaultHandler will correctly handle), we need -// to guard against this by unprotecting the code (if it has been protected) and -// preventing it from being protected while we are touching it. -class AutoUnprotectCode -{ - JSRuntime *rt_; - JSRuntime::AutoLockForInterrupt lock_; - const AsmJSModule &module_; - const bool protectedBefore_; - - public: - AutoUnprotectCode(JSContext *cx, const AsmJSModule &module) - : rt_(cx->runtime()), - lock_(rt_), - module_(module), - protectedBefore_(module_.codeIsProtected(rt_)) - { - if (protectedBefore_) - module_.unprotectCode(rt_); - } - - ~AutoUnprotectCode() - { - if (protectedBefore_) - module_.protectCode(rt_); - } -}; - static uint8_t * AllocateExecutableMemory(ExclusiveContext *cx, size_t bytes) { #ifdef XP_WIN unsigned permissions = PAGE_EXECUTE_READWRITE; #else unsigned permissions = PROT_READ | PROT_WRITE | PROT_EXEC; #endif @@ -108,18 +78,17 @@ AsmJSModule::AsmJSModule(ScriptSource *s bufferArgumentName_(nullptr), code_(nullptr), interruptExit_(nullptr), prevLinked_(nullptr), nextLinked_(nullptr), dynamicallyLinked_(false), loadedFromCache_(false), profilingEnabled_(false), - interrupted_(false), - codeIsProtected_(false) + interrupted_(false) { mozilla::PodZero(&pod); pod.funcPtrTableAndExitBytes_ = SIZE_MAX; pod.functionBytes_ = UINT32_MAX; pod.minHeapLength_ = RoundUpToNextValidAsmJSHeapLength(0); pod.maxHeapLength_ = 0x80000000; pod.strict_ = strict; pod.usesSignalHandlers_ = canUseSignalHandlers; @@ -825,17 +794,16 @@ AsmJSModule::initHeap(Handle<ArrayBuffer uint32_t heapLength = heap->byteLength(); for (unsigned i = 0; i < heapAccesses_.length(); i++) { jit::Assembler::UpdateBoundsCheck(heapLength, (jit::Instruction*)(heapAccesses_[i].offset() + code_)); } #endif } -// This method assumes the caller has a live AutoUnprotectCode. void AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer) { #if defined(JS_CODEGEN_X86) if (maybePrevBuffer) { // Subtract out the base-pointer added by AsmJSModule::initHeap. uint8_t *ptrBase = maybePrevBuffer->dataPointer(); for (unsigned i = 0; i < heapAccesses_.length(); i++) { @@ -847,17 +815,16 @@ AsmJSModule::restoreHeapToInitialState(A } } #endif maybeHeap_ = nullptr; heapDatum() = nullptr; } -// This method assumes the caller has a live AutoUnprotectCode. void AsmJSModule::restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer, uint8_t *prevCode, ExclusiveContext *cx) { #ifdef DEBUG // Put the absolute links back to -1 so PatchDataWithValueCheck assertions // in staticallyLink are valid. @@ -902,17 +869,16 @@ AsmJSModule::detachHeap(JSContext *cx) } // Even if this->active(), to reach here, the activation must have called // out via an FFI stub. FFI stubs check if heapDatum() is null on reentry // and throw an exception if so. MOZ_ASSERT_IF(active(), activation()->exitReason() == AsmJSExit::Reason_IonFFI || activation()->exitReason() == AsmJSExit::Reason_SlowFFI); - AutoUnprotectCode auc(cx, *this); restoreHeapToInitialState(maybeHeap_); MOZ_ASSERT(hasDetachedHeap()); return true; } bool js::OnDetachAsmJSArrayBuffer(JSContext *cx, Handle<ArrayBufferObject*> buffer) @@ -1563,18 +1529,16 @@ AsmJSModule::deserialize(ExclusiveContex loadedFromCache_ = true; return cursor; } bool AsmJSModule::clone(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *moduleOut) const { - AutoUnprotectCode auc(cx, *this); - *moduleOut = cx->new_<AsmJSModule>(scriptSource_, srcStart_, srcBodyStart_, pod.strict_, pod.usesSignalHandlers_); if (!*moduleOut) return false; AsmJSModule &out = **moduleOut; // Mirror the order of serialize/deserialize in cloning: @@ -1623,17 +1587,16 @@ AsmJSModule::changeHeap(Handle<ArrayBuff // Content JS should not be able to run (and change heap) from within an // interrupt callback, but in case it does, fail to change heap. Otherwise, // the heap can change at every single instruction which would prevent // future optimizations like heap-base hoisting. if (interrupted_) return false; - AutoUnprotectCode auc(cx, *this); restoreHeapToInitialState(maybeHeap_); initHeap(newHeap, cx); return true; } void AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx) { @@ -1664,19 +1627,16 @@ AsmJSModule::setProfilingEnabled(bool en } else { profilingLabels_.clear(); } // Conservatively flush the icache for the entire module. AutoFlushICache afc("AsmJSModule::setProfilingEnabled"); setAutoFlushICacheRange(); - // To enable profiling, we need to patch 3 kinds of things: - AutoUnprotectCode auc(cx, *this); - // Patch all internal (asm.js->asm.js) callsites to call the profiling // prologues: for (size_t i = 0; i < callSites_.length(); i++) { CallSite &cs = callSites_[i]; if (cs.kind() != CallSite::Relative) continue; uint8_t *callerRetAddr = code_ + cs.returnAddressOffset(); @@ -1813,69 +1773,16 @@ AsmJSModule::setProfilingEnabled(bool en PatchedImmPtr(to), PatchedImmPtr(from)); } } profilingEnabled_ = enabled; } -void -AsmJSModule::protectCode(JSRuntime *rt) const -{ - MOZ_ASSERT(isDynamicallyLinked()); - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - - codeIsProtected_ = true; - - if (!pod.functionBytes_) - return; - - // Technically, we should be able to only take away the execute permissions, - // however this seems to break our emulators which don't always check - // execute permissions while executing code. -#if defined(XP_WIN) - DWORD oldProtect; - if (!VirtualProtect(codeBase(), functionBytes(), PAGE_NOACCESS, &oldProtect)) - MOZ_CRASH(); -#else // assume Unix - if (mprotect(codeBase(), functionBytes(), PROT_NONE)) - MOZ_CRASH(); -#endif -} - -void -AsmJSModule::unprotectCode(JSRuntime *rt) const -{ - MOZ_ASSERT(isDynamicallyLinked()); - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - - codeIsProtected_ = false; - - if (!pod.functionBytes_) - return; - -#if defined(XP_WIN) - DWORD oldProtect; - if (!VirtualProtect(codeBase(), functionBytes(), PAGE_EXECUTE_READWRITE, &oldProtect)) - MOZ_CRASH(); -#else // assume Unix - if (mprotect(codeBase(), functionBytes(), PROT_READ | PROT_WRITE | PROT_EXEC)) - MOZ_CRASH(); -#endif -} - -bool -AsmJSModule::codeIsProtected(JSRuntime *rt) const -{ - MOZ_ASSERT(isDynamicallyLinked()); - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - return codeIsProtected_; -} - static bool GetCPUID(uint32_t *cpuId) { enum Arch { X86 = 0x1, X64 = 0x2, ARM = 0x3, MIPS = 0x4,
--- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -821,20 +821,16 @@ class AsmJSModule HeapPtrArrayBufferObjectMaybeShared maybeHeap_; AsmJSModule ** prevLinked_; AsmJSModule * nextLinked_; bool dynamicallyLinked_; bool loadedFromCache_; bool profilingEnabled_; bool interrupted_; - // This field is accessed concurrently when requesting an interrupt. - // Access must be synchronized via the runtime's interrupt lock. - mutable bool codeIsProtected_; - void restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer); void restoreToInitialState(ArrayBufferObjectMaybeShared *maybePrevBuffer, uint8_t *prevCode, ExclusiveContext *cx); public: explicit AsmJSModule(ScriptSource *scriptSource, uint32_t srcStart, uint32_t srcBodyStart, bool strict, bool canUseSignalHandlers); void trace(JSTracer *trc); @@ -1524,22 +1520,16 @@ class AsmJSModule MOZ_ASSERT(isDynamicallyLinked()); return profilingEnabled_; } void setProfilingEnabled(bool enabled, JSContext *cx); void setInterrupted(bool interrupted) { MOZ_ASSERT(isDynamicallyLinked()); interrupted_ = interrupted; } - - // Additionally, these functions may only be called while holding the - // runtime's interrupt lock. - void protectCode(JSRuntime *rt) const; - void unprotectCode(JSRuntime *rt) const; - bool codeIsProtected(JSRuntime *rt) const; }; // Store the just-parsed module in the cache using AsmJSCacheOps. extern JS::AsmJSCacheResult StoreAsmJSModuleInCache(AsmJSParser &parser, const AsmJSModule &module, ExclusiveContext *cx);
--- a/js/src/asmjs/AsmJSSignalHandlers.cpp +++ b/js/src/asmjs/AsmJSSignalHandlers.cpp @@ -14,25 +14,71 @@ * 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/AsmJSSignalHandlers.h" #include "mozilla/DebugOnly.h" +#include "mozilla/PodOperations.h" #include "asmjs/AsmJSModule.h" #include "vm/Runtime.h" using namespace js; using namespace js::jit; using JS::GenericNaN; using mozilla::DebugOnly; +using mozilla::PodArrayZero; + +#if defined(ANDROID) +# include <sys/system_properties.h> +# if defined(MOZ_LINKER) +extern "C" MFBT_API bool IsSignalHandlingBroken(); +# endif +#endif + +// For platforms where the signal/exception handler runs on the same +// thread/stack as the victim (Unix and Windows), we can use TLS to find any +// currently executing asm.js code. +static JSRuntime * +RuntimeForCurrentThread() +{ + PerThreadData *threadData = TlsPerThreadData.get(); + if (!threadData) + return nullptr; + + return threadData->runtimeIfOnOwnerThread(); +} + +// Crashing inside the signal handler can cause the handler to be recursively +// invoked, eventually blowing the stack without actually showing a crash +// report dialog via Breakpad. To guard against this we watch for such +// recursion and fall through to the next handler immediately rather than +// trying to handle it. +class AutoSetHandlingSignal +{ + JSRuntime *rt; + + public: + explicit AutoSetHandlingSignal(JSRuntime *rt) + : rt(rt) + { + MOZ_ASSERT(!rt->handlingSignal); + rt->handlingSignal = true; + } + + ~AutoSetHandlingSignal() + { + MOZ_ASSERT(rt->handlingSignal); + rt->handlingSignal = false; + } +}; #if defined(XP_WIN) # define XMM_sig(p,i) ((p)->Xmm##i) # define EIP_sig(p) ((p)->Eip) # define RIP_sig(p) ((p)->Rip) # define RAX_sig(p) ((p)->Rax) # define RCX_sig(p) ((p)->Rcx) # define RDX_sig(p) ((p)->Rdx) @@ -147,93 +193,34 @@ using mozilla::DebugOnly; # define R13_sig(p) ((p)->uc_mcontext.mc_r13) # define R14_sig(p) ((p)->uc_mcontext.mc_r14) # if defined(__FreeBSD__) && defined(__arm__) # define R15_sig(p) ((p)->uc_mcontext.__gregs[_REG_R15]) # else # define R15_sig(p) ((p)->uc_mcontext.mc_r15) # endif #elif defined(XP_MACOSX) -// Mach requires special treatment. +# define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) +# define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) #else # error "Don't know how to read/write to the thread state via the mcontext_t." #endif -// For platforms where the signal/exception handler runs on the same -// thread/stack as the victim (Unix and Windows), we can use TLS to find any -// currently executing asm.js code. -#if !defined(XP_MACOSX) -static JSRuntime * -RuntimeForCurrentThread() -{ - PerThreadData *threadData = TlsPerThreadData.get(); - if (!threadData) - return nullptr; - - return threadData->runtimeIfOnOwnerThread(); -} -#endif // !defined(XP_MACOSX) - -// Crashing inside the signal handler can cause the handler to be recursively -// invoked, eventually blowing the stack without actually showing a crash -// report dialog via Breakpad. To guard against this we watch for such -// recursion and fall through to the next handler immediately rather than -// trying to handle it. -class AutoSetHandlingSignal -{ - JSRuntime *rt; - - public: - explicit AutoSetHandlingSignal(JSRuntime *rt) - : rt(rt) - { - MOZ_ASSERT(!rt->handlingSignal); - rt->handlingSignal = true; - } - - ~AutoSetHandlingSignal() - { - MOZ_ASSERT(rt->handlingSignal); - rt->handlingSignal = false; - } -}; - -#if defined(JS_CODEGEN_X64) -template <class T> -static void -SetXMMRegToNaN(bool isFloat32, T *xmm_reg) -{ - if (isFloat32) { - JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float)); - float *floats = reinterpret_cast<float*>(xmm_reg); - floats[0] = GenericNaN(); - floats[1] = 0; - floats[2] = 0; - floats[3] = 0; - } else { - JS_STATIC_ASSERT(sizeof(T) == 2 * sizeof(double)); - double *dbls = reinterpret_cast<double*>(xmm_reg); - dbls[0] = GenericNaN(); - dbls[1] = 0; - } -} -#endif - #if defined(XP_WIN) # include "jswin.h" #else # include <signal.h> # include <sys/mman.h> #endif #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) # include <sys/ucontext.h> // for ucontext_t, mcontext_t #endif -#if defined(JS_CODEGEN_X64) +#if defined(JS_CPU_X64) # if defined(__DragonFly__) # include <machine/npx.h> // for union savefpu # elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ defined(__NetBSD__) || defined(__OpenBSD__) # include <machine/fpu.h> // for struct savefpu/fxsave64 # endif #endif @@ -312,77 +299,57 @@ typedef struct ucontext { mcontext_t uc_mcontext; // Other fields are not used by V8, don't define them here. } ucontext_t; enum { REG_EIP = 14 }; # endif // defined(__i386__) # endif // !defined(__BIONIC_HAVE_UCONTEXT_T) #endif // defined(ANDROID) -#if defined(ANDROID) && defined(MOZ_LINKER) -// Apparently, on some Android systems, the signal handler is always passed -// nullptr as the faulting address. This would cause the asm.js signal handler -// to think that a safe out-of-bounds access was a nullptr-deref. This -// brokenness is already detected by ElfLoader (enabled by MOZ_LINKER), so -// reuse that check to disable asm.js compilation on systems where the signal -// handler is broken. -extern "C" MFBT_API bool IsSignalHandlingBroken(); -#else -static bool IsSignalHandlingBroken() { return false; } -#endif // defined(MOZ_LINKER) - #if !defined(XP_WIN) # define CONTEXT ucontext_t #endif #if defined(JS_CPU_X64) # define PC_sig(p) RIP_sig(p) #elif defined(JS_CPU_X86) # define PC_sig(p) EIP_sig(p) #elif defined(JS_CPU_ARM) # define PC_sig(p) R15_sig(p) #elif defined(JS_CPU_MIPS) # define PC_sig(p) EPC_sig(p) #endif -static bool -HandleSimulatorInterrupt(JSRuntime *rt, AsmJSActivation *activation, void *faultingAddress) -{ - // If the ARM simulator is enabled, the pc is in the simulator C++ code and - // not in the generated code, so we check the simulator's pc manually. Also - // note that we can't simply use simulator->set_pc() here because the - // simulator could be in the middle of an instruction. On ARM, the signal - // handlers are currently only used for Odin code, see bug 964258. - -#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) - const AsmJSModule &module = activation->module(); - if (module.containsFunctionPC((void *)rt->mainThread.simulator()->get_pc()) && - module.containsFunctionPC(faultingAddress)) - { - activation->setResumePC(nullptr); - int32_t nextpc = int32_t(module.interruptExit()); - rt->mainThread.simulator()->set_resume_pc(nextpc); - return true; - } -#endif - return false; -} - -#if !defined(XP_MACOSX) static uint8_t ** ContextToPC(CONTEXT *context) { -#ifdef JS_CODEGEN_NONE - MOZ_CRASH(); -#else return reinterpret_cast<uint8_t**>(&PC_sig(context)); -#endif } -# if defined(JS_CODEGEN_X64) +#if defined(JS_CPU_X64) +template <class T> +static void +SetXMMRegToNaN(bool isFloat32, T *xmm_reg) +{ + if (isFloat32) { + JS_STATIC_ASSERT(sizeof(T) == 4 * sizeof(float)); + float *floats = reinterpret_cast<float*>(xmm_reg); + floats[0] = GenericNaN(); + floats[1] = 0; + floats[2] = 0; + floats[3] = 0; + } else { + JS_STATIC_ASSERT(sizeof(T) == 2 * sizeof(double)); + double *dbls = reinterpret_cast<double*>(xmm_reg); + dbls[0] = GenericNaN(); + dbls[1] = 0; + } +} + +# if !defined(XP_MACOSX) static void SetRegisterToCoercedUndefined(CONTEXT *context, bool isFloat32, AnyRegister reg) { if (reg.isFloat()) { switch (reg.fpu().code()) { case X86Registers::xmm0: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 0)); break; case X86Registers::xmm1: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 1)); break; case X86Registers::xmm2: SetXMMRegToNaN(isFloat32, &XMM_sig(context, 2)); break; @@ -418,74 +385,55 @@ SetRegisterToCoercedUndefined(CONTEXT *c case X86Registers::r12: R12_sig(context) = 0; break; case X86Registers::r13: R13_sig(context) = 0; break; case X86Registers::r14: R14_sig(context) = 0; break; case X86Registers::r15: R15_sig(context) = 0; break; default: MOZ_CRASH(); } } } -# endif // JS_CODEGEN_X64 -#endif // !XP_MACOSX +# endif // !XP_MACOSX +#endif // JS_CPU_X64 #if defined(XP_WIN) static bool -HandleException(PEXCEPTION_POINTERS exception) +HandleFault(PEXCEPTION_POINTERS exception) { EXCEPTION_RECORD *record = exception->ExceptionRecord; CONTEXT *context = exception->ContextRecord; if (record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) return false; uint8_t **ppc = ContextToPC(context); uint8_t *pc = *ppc; MOZ_ASSERT(pc == record->ExceptionAddress); if (record->NumberParameters < 2) return false; - void *faultingAddress = (void*)record->ExceptionInformation[1]; - + // Don't allow recursive handling of signals, see AutoSetHandlingSignal. JSRuntime *rt = RuntimeForCurrentThread(); - - // Don't allow recursive handling of signals, see AutoSetHandlingSignal. if (!rt || rt->handlingSignal) return false; AutoSetHandlingSignal handling(rt); - if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress)) - return true; - - AsmJSActivation *activation = PerThreadData::innermostAsmJSActivation(); + AsmJSActivation *activation = rt->mainThread.asmJSActivationStack(); if (!activation) return false; const AsmJSModule &module = activation->module(); if (!module.containsFunctionPC(pc)) return false; - // If we faulted trying to execute code in 'module', this must be an - // interrupt callback (see RequestInterruptForAsmJSCode). Redirect - // execution to a trampoline which will call js::HandleExecutionInterrupt. - // The trampoline will jump to activation->resumePC if execution isn't - // interrupted. - if (module.containsFunctionPC(faultingAddress)) { - activation->setResumePC(pc); - *ppc = module.interruptExit(); - - JSRuntime::AutoLockForInterrupt lock(rt); - module.unprotectCode(rt); - return true; - } - -# if defined(JS_CODEGEN_X64) +# if defined(JS_CPU_X64) // These checks aren't necessary, but, since we can, check anyway to make // sure we aren't covering up a real bug. + void *faultingAddress = (void*)record->ExceptionInformation[1]; if (!module.maybeHeap() || faultingAddress < module.maybeHeap() || faultingAddress >= module.maybeHeap() + AsmJSMappedSize) { return false; } const AsmJSHeapAccess *heapAccess = module.lookupHeapAccess(pc); @@ -507,43 +455,41 @@ HandleException(PEXCEPTION_POINTERS exce *ppc += heapAccess->opLength(); return true; # else return false; # endif } static LONG WINAPI -AsmJSExceptionHandler(LPEXCEPTION_POINTERS exception) +AsmJSFaultHandler(LPEXCEPTION_POINTERS exception) { - if (HandleException(exception)) + if (HandleFault(exception)) return EXCEPTION_CONTINUE_EXECUTION; // No need to worry about calling other handlers, the OS does this for us. return EXCEPTION_CONTINUE_SEARCH; } #elif defined(XP_MACOSX) # include <mach/exc.h> static uint8_t ** ContextToPC(x86_thread_state_t &state) { -# if defined(JS_CODEGEN_X64) +# if defined(JS_CPU_X64) JS_STATIC_ASSERT(sizeof(state.uts.ts64.__rip) == sizeof(void*)); return reinterpret_cast<uint8_t**>(&state.uts.ts64.__rip); -# elif defined(JS_CODEGEN_NONE) - MOZ_CRASH(); # else JS_STATIC_ASSERT(sizeof(state.uts.ts32.__eip) == sizeof(void*)); return reinterpret_cast<uint8_t**>(&state.uts.ts32.__eip); # endif } -# if defined(JS_CODEGEN_X64) +# if defined(JS_CPU_X64) static bool SetRegisterToCoercedUndefined(mach_port_t rtThread, x86_thread_state64_t &state, const AsmJSHeapAccess &heapAccess) { if (heapAccess.loadedReg().isFloat()) { kern_return_t kret; x86_float_state64_t fstate; @@ -645,55 +591,28 @@ HandleMachException(JSRuntime *rt, const return false; uint8_t **ppc = ContextToPC(state); uint8_t *pc = *ppc; if (request.body.exception != EXC_BAD_ACCESS || request.body.codeCnt != 2) return false; - void *faultingAddress = (void*)request.body.code[1]; - - if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress)) - return true; - AsmJSActivation *activation = rt->mainThread.asmJSActivationStack(); if (!activation) return false; const AsmJSModule &module = activation->module(); - if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) { - JSRuntime::AutoLockForInterrupt lock(rt); - module.unprotectCode(rt); - return true; - } - if (!module.containsFunctionPC(pc)) return false; - // If we faulted trying to execute code in 'module', this must be an - // interrupt callback (see RequestInterruptForAsmJSCode). Redirect - // execution to a trampoline which will call js::HandleExecutionInterrupt. - // The trampoline will jump to activation->resumePC if execution isn't - // interrupted. - if (module.containsFunctionPC(faultingAddress)) { - activation->setResumePC(pc); - *ppc = module.interruptExit(); - - JSRuntime::AutoLockForInterrupt lock(rt); - module.unprotectCode(rt); - - // Update the thread state with the new pc. - kret = thread_set_state(rtThread, x86_THREAD_STATE, (thread_state_t)&state, x86_THREAD_STATE_COUNT); - return kret == KERN_SUCCESS; - } - -# if defined(JS_CODEGEN_X64) +# if defined(JS_CPU_X64) // These checks aren't necessary, but, since we can, check anyway to make // sure we aren't covering up a real bug. + void *faultingAddress = (void*)request.body.code[1]; if (!module.maybeHeap() || faultingAddress < module.maybeHeap() || faultingAddress >= module.maybeHeap() + AsmJSMappedSize) { return false; } const AsmJSHeapAccess *heapAccess = module.lookupHeapAccess(pc); @@ -874,65 +793,40 @@ AsmJSMachExceptionHandler::install(JSRun return false; } #else // If not Windows or Mac, assume Unix // Be very cautious and default to not handling; we don't want to accidentally // silence real crashes from real bugs. static bool -HandleSignal(int signum, siginfo_t *info, void *ctx) +HandleFault(int signum, siginfo_t *info, void *ctx) { CONTEXT *context = (CONTEXT *)ctx; uint8_t **ppc = ContextToPC(context); uint8_t *pc = *ppc; - void *faultingAddress = info->si_addr; - + // Don't allow recursive handling of signals, see AutoSetHandlingSignal. JSRuntime *rt = RuntimeForCurrentThread(); - - // Don't allow recursive handling of signals, see AutoSetHandlingSignal. if (!rt || rt->handlingSignal) return false; AutoSetHandlingSignal handling(rt); - if (rt->jitRuntime() && rt->jitRuntime()->handleAccessViolation(rt, faultingAddress)) - return true; - - AsmJSActivation *activation = PerThreadData::innermostAsmJSActivation(); + AsmJSActivation *activation = rt->mainThread.asmJSActivationStack(); if (!activation) return false; const AsmJSModule &module = activation->module(); - if (HandleSimulatorInterrupt(rt, activation, faultingAddress)) { - JSRuntime::AutoLockForInterrupt lock(rt); - module.unprotectCode(rt); - return true; - } - if (!module.containsFunctionPC(pc)) return false; - // If we faulted trying to execute code in 'module', this must be an - // interrupt callback (see RequestInterruptForAsmJSCode). Redirect - // execution to a trampoline which will call js::HandleExecutionInterrupt. - // The trampoline will jump to activation->resumePC if execution isn't - // interrupted. - if (module.containsFunctionPC(faultingAddress)) { - activation->setResumePC(pc); - *ppc = module.interruptExit(); - - JSRuntime::AutoLockForInterrupt lock(rt); - module.unprotectCode(rt); - return true; - } - -# if defined(JS_CODEGEN_X64) +# if defined(JS_CPU_X64) // These checks aren't necessary, but, since we can, check anyway to make // sure we aren't covering up a real bug. + void *faultingAddress = info->si_addr; if (!module.maybeHeap() || faultingAddress < module.maybeHeap() || faultingAddress >= module.maybeHeap() + AsmJSMappedSize) { return false; } const AsmJSHeapAccess *heapAccess = module.lookupHeapAccess(pc); @@ -949,120 +843,231 @@ HandleSignal(int signum, siginfo_t *info SetRegisterToCoercedUndefined(context, heapAccess->isFloat32Load(), heapAccess->loadedReg()); *ppc += heapAccess->opLength(); return true; # else return false; # endif } -static struct sigaction sPrevHandler; +static struct sigaction sPrevSEGVHandler; static void AsmJSFaultHandler(int signum, siginfo_t *info, void *context) { - if (HandleSignal(signum, info, context)) + if (HandleFault(signum, info, context)) return; // This signal is not for any asm.js code we expect, so we need to forward // the signal to the next handler. If there is no next handler (SIG_IGN or // SIG_DFL), then it's time to crash. To do this, we set the signal back to // its original disposition and return. This will cause the faulting op to // be re-executed which will crash in the normal way. The advantage of // doing this to calling _exit() is that we remove ourselves from the crash // stack which improves crash reports. If there is a next handler, call it. // It will either crash synchronously, fix up the instruction so that // execution can continue and return, or trigger a crash by returning the // signal to it's original disposition and returning. // // Note: the order of these tests matter. - if (sPrevHandler.sa_flags & SA_SIGINFO) - sPrevHandler.sa_sigaction(signum, info, context); - else if (sPrevHandler.sa_handler == SIG_DFL || sPrevHandler.sa_handler == SIG_IGN) - sigaction(signum, &sPrevHandler, nullptr); + if (sPrevSEGVHandler.sa_flags & SA_SIGINFO) + sPrevSEGVHandler.sa_sigaction(signum, info, context); + else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN) + sigaction(signum, &sPrevSEGVHandler, nullptr); else - sPrevHandler.sa_handler(signum); + sPrevSEGVHandler.sa_handler(signum); } #endif -#if !defined(XP_MACOSX) -static bool sInstalledHandlers = false; +static void +RedirectIonBackedgesToInterruptCheck(JSRuntime *rt) +{ + if (jit::JitRuntime *jitRuntime = rt->jitRuntime()) { + // If the backedge list is being mutated, the pc must be in C++ code and + // thus not in a JIT iloop. We assume that the interrupt flag will be + // checked at least once before entering JIT code (if not, no big deal; + // the browser will just request another interrupt in a second). + if (!jitRuntime->mutatingBackedgeList()) + jitRuntime->patchIonBackedges(rt, jit::JitRuntime::BackedgeInterruptCheck); + } +} + +static void +RedirectJitCodeToInterruptCheck(JSRuntime *rt, CONTEXT *context) +{ + RedirectIonBackedgesToInterruptCheck(rt); + + if (AsmJSActivation *activation = rt->mainThread.asmJSActivationStack()) { + const AsmJSModule &module = activation->module(); + +#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) + if (module.containsFunctionPC((void*)rt->mainThread.simulator()->get_pc())) + rt->mainThread.simulator()->set_resume_pc(int32_t(module.interruptExit())); +#endif + + uint8_t **ppc = ContextToPC(context); + uint8_t *pc = *ppc; + if (module.containsFunctionPC(pc)) { + activation->setResumePC(pc); + *ppc = module.interruptExit(); + } + } +} + +#if !defined(XP_WIN) +// For the interrupt signal, pick a signal number that: +// - is not otherwise used by mozilla or standard libraries +// - defaults to nostop and noprint on gdb/lldb so that noone is bothered +// SIGVTALRM a relative of SIGALRM, so intended for user code, but, unlike +// SIGALRM, not used anywhere else in Mozilla. +static const int sInterruptSignal = SIGVTALRM; + +static void +JitInterruptHandler(int signum, siginfo_t *info, void *context) +{ + if (JSRuntime *rt = RuntimeForCurrentThread()) + RedirectJitCodeToInterruptCheck(rt, (CONTEXT*)context); +} #endif bool -js::EnsureAsmJSSignalHandlersInstalled(JSRuntime *rt) +js::EnsureSignalHandlersInstalled(JSRuntime *rt) { -#ifdef JS_CODEGEN_NONE - // Don't install signal handlers in builds with the JIT disabled. - return false; +#if defined(XP_MACOSX) + // On OSX, each JSRuntime gets its own handler thread. + if (!rt->asmJSMachExceptionHandler.installed() && !rt->asmJSMachExceptionHandler.install(rt)) + return false; #endif + // All the rest of the handlers are process-wide and thus must only be + // installed once. We assume that there are no races creating the first + // JSRuntime of the process. + static bool sTried = false; + static bool sResult = false; + if (sTried) + return sResult; + sTried = true; + +#if defined(ANDROID) + // Before Android 4.4 (SDK version 19), there is a bug + // https://android-review.googlesource.com/#/c/52333 + // in Bionic's pthread_join which causes pthread_join to return early when + // pthread_kill is used (on any thread). Nobody expects the pthread_cond_wait + // EINTRquisition. + char version_string[PROP_VALUE_MAX]; + PodArrayZero(version_string); + if (__system_property_get("ro.build.version.sdk", version_string) > 0) { + if (atol(version_string) < 19) + return false; + } +# if defined(MOZ_LINKER) + // Signal handling is broken on some android systems. if (IsSignalHandlingBroken()) return false; +# endif +#endif -#if defined(XP_MACOSX) - // On OSX, each JSRuntime gets its own handler. - return rt->asmJSMachExceptionHandler.installed() || rt->asmJSMachExceptionHandler.install(rt); +#if defined(XP_WIN) + // Windows uses SuspendThread to stop the main thread from another thread, + // so the only handler we need is for asm.js out-of-bound faults. + if (!AddVectoredExceptionHandler(/* FirstHandler = */true, AsmJSFaultHandler)) + MOZ_CRASH("unable to install vectored exception handler"); #else - // Assume Windows or Unix. For these platforms, there is a single, - // process-wide signal handler installed. Take care to only install it once. - if (sInstalledHandlers) - return true; + // The interrupt handler allows the main thread to be paused from another + // thread (see InterruptRunningJitCode). + struct sigaction interruptHandler; + interruptHandler.sa_flags = SA_SIGINFO; + interruptHandler.sa_sigaction = &JitInterruptHandler; + sigemptyset(&interruptHandler.sa_mask); + struct sigaction prev; + if (sigaction(sInterruptSignal, &interruptHandler, &prev)) + MOZ_CRASH("unable to install interrupt handler"); -# if defined(XP_WIN) - if (!AddVectoredExceptionHandler(/* FirstHandler = */true, AsmJSExceptionHandler)) - return false; -# else - // Assume Unix. SA_NODEFER allows us to reenter the signal handler if we - // crash while handling the signal, and fall through to the Breakpad - // handler by testing handlingSignal. - struct sigaction sigAction; - sigAction.sa_flags = SA_SIGINFO | SA_NODEFER; - sigAction.sa_sigaction = &AsmJSFaultHandler; - sigemptyset(&sigAction.sa_mask); - if (sigaction(SIGSEGV, &sigAction, &sPrevHandler)) - return false; -# endif + // There shouldn't be any other handlers installed for sInterruptSignal. If + // there are, we could always forward, but we need to understand what we're + // doing to avoid problematic interference. + if ((prev.sa_flags & SA_SIGINFO && prev.sa_sigaction) || + (prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN)) + { + MOZ_CRASH("contention for interrupt signal"); + } - sInstalledHandlers = true; -#endif + // Lastly, install a SIGSEGV handler to handle safely-out-of-bounds asm.js + // heap access. OSX handles seg faults via the Mach exception handler above, + // so don't install AsmJSFaultHandler. +# if !defined(XP_MACOSX) + // SA_NODEFER allows us to reenter the signal handler if we crash while + // handling the signal, and fall through to the Breakpad handler by testing + // handlingSignal. + struct sigaction faultHandler; + faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER; + faultHandler.sa_sigaction = &AsmJSFaultHandler; + sigemptyset(&faultHandler.sa_mask); + if (sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler)) + MOZ_CRASH("unable to install segv handler"); +# endif // defined(XP_MACOSX) +#endif // defined(XP_WIN) + + sResult = true; return true; } -// To interrupt execution of a JSRuntime, any thread may call -// JS_RequestInterruptCallback (JSRuntime::requestInterruptCallback from inside -// the engine). In the simplest case, this sets some state that is polled at -// regular intervals (function prologues, loop headers). For tight loops, this -// poses non-trivial overhead. For asm.js, we can do better: when another -// thread requests an interrupt, we simply mprotect all of the innermost asm.js -// module activation's code. This will trigger a SIGSEGV, taking us into -// AsmJSFaultHandler. From there, we can manually redirect execution to call -// js::HandleExecutionInterrupt. The memory is un-protected from the signal -// handler after control flow is redirected. +// JSRuntime::requestInterrupt sets interrupt_ (which is checked frequently by +// C++ code at every Baseline JIT loop backedge) and jitStackLimit_ (which is +// checked at every Baseline and Ion JIT function prologue). The remaining +// sources of potential iloops (Ion loop backedges and all asm.js code) are +// handled by this function: +// 1. Ion loop backedges are patched to instead point to a stub that handles the +// interrupt; +// 2. if the main thread's pc is inside asm.js code, the pc is updated to point +// to a stub that handles the interrupt. void -js::RequestInterruptForAsmJSCode(JSRuntime *rt, int interruptModeRaw) +js::InterruptRunningJitCode(JSRuntime *rt) { - switch (JSRuntime::InterruptMode(interruptModeRaw)) { - case JSRuntime::RequestInterruptMainThread: - case JSRuntime::RequestInterruptAnyThread: - break; - case JSRuntime::RequestInterruptAnyThreadDontStopIon: - case JSRuntime::RequestInterruptAnyThreadForkJoin: - // It is ok to wait for asm.js execution to complete; we aren't trying - // to break an iloop or anything. Avoid the overhead of protecting all - // the code and taking a fault. + // If signal handlers weren't installed, then Ion and asm.js emit normal + // interrupt checks and don't need asynchronous interruption. + if (!rt->canUseSignalHandlers()) + return; + + // If we are on runtime's main thread, then: pc is not in asm.js code (so + // nothing to do for asm.js) and we can patch Ion backedges without any + // special synchronization. + if (rt == RuntimeForCurrentThread()) { + RedirectIonBackedgesToInterruptCheck(rt); return; } - AsmJSActivation *activation = rt->mainThread.asmJSActivationStack(); - if (!activation) - return; + // We are not on the runtime's main thread, so to do 1 and 2 above, we need + // to halt the runtime's main thread first. +#if defined(XP_WIN) + // On Windows, we can simply suspend the main thread and work directly on + // its context from this thread. + HANDLE thread = (HANDLE)rt->ownerThreadNative(); + if (SuspendThread(thread) == -1) + MOZ_CRASH("Failed to suspend main thread"); + + CONTEXT context; + context.ContextFlags = CONTEXT_CONTROL; + if (!GetThreadContext(thread, &context)) + MOZ_CRASH("Failed to get suspended thread context"); - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - activation->module().protectCode(rt); + RedirectJitCodeToInterruptCheck(rt, &context); + + if (!SetThreadContext(thread, &context)) + MOZ_CRASH("Failed to set suspended thread context"); + + if (ResumeThread(thread) == -1) + MOZ_CRASH("Failed to resume main thread"); +#else + // On Unix, we instead deliver an async signal to the main thread which + // halts the thread and callers our JitInterruptHandler (which has already + // been installed by EnsureSignalHandlersInstalled). + pthread_t thread = (pthread_t)rt->ownerThreadNative(); + pthread_kill(thread, sInterruptSignal); +#endif } // This is not supported by clang-cl yet. #if defined(MOZ_ASAN) && defined(JS_STANDALONE) && !defined(_MSC_VER) // Usually, this definition is found in mozglue (see mozglue/build/AsanOptions.cpp). // However, when doing standalone JS builds, mozglue is not used and we must ensure // that we still allow custom SIGSEGV handlers for asm.js and ion to work correctly. extern "C" MOZ_ASAN_BLACKLIST
--- a/js/src/asmjs/AsmJSSignalHandlers.h +++ b/js/src/asmjs/AsmJSSignalHandlers.h @@ -23,25 +23,26 @@ struct JSRuntime; #ifdef XP_MACOSX # include <mach/mach.h> # include "jslock.h" #endif namespace js { -// Returns whether signal handlers for asm.js and for JitRuntime access -// violations have been installed. +// Set up any signal/exception handlers needed to execute code in the given +// runtime. Return whether runtime can: +// - rely on fault handler support for avoiding asm.js heap bounds checks +// - rely on InterruptRunningJitCode to halt running Ion/asm.js from any thread bool -EnsureAsmJSSignalHandlersInstalled(JSRuntime *rt); +EnsureSignalHandlersInstalled(JSRuntime *rt); -// Force any currently-executing asm.js code to call -// js::HandleExecutionInterrupt. +// Force any currently-executing asm.js code to call HandleExecutionInterrupt. extern void -RequestInterruptForAsmJSCode(JSRuntime *rt, int interruptMode); +InterruptRunningJitCode(JSRuntime *rt); // On OSX we are forced to use the lower-level Mach exception mechanism instead // of Unix signals. Mach exceptions are not handled on the victim's stack but // rather require an extra thread. For simplicity, we create one such thread // per JSRuntime (upon the first use of asm.js in the JSRuntime). This thread // and related resources are owned by AsmJSMachExceptionHandler which is owned // by JSRuntime. #ifdef XP_MACOSX
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -7619,30 +7619,16 @@ CodeGenerator::link(JSContext *cx, types safepointIndices_.length(), osiIndices_.length(), cacheList_.length(), runtimeData_.length(), safepoints_.size(), callTargets.length(), patchableBackedges_.length(), optimizationLevel); if (!ionScript) return false; discardIonCode.ionScript = ionScript; - // Lock the runtime against interrupt callbacks during the link. - // We don't want an interrupt request to protect the code for the script - // before it has been filled in, as we could segv before the runtime's - // patchable backedges have been fully updated. - JSRuntime::AutoLockForInterrupt lock(cx->runtime()); - - // Make sure we don't segv while filling in the code, to avoid deadlocking - // inside the signal handler. - cx->runtime()->jitRuntime()->ensureIonCodeAccessible(cx->runtime()); - - // Implicit interrupts are used only for sequential code. In parallel mode - // use the normal executable allocator so that we cannot segv during - // execution off the main thread. - // // Also, note that creating the code here during an incremental GC will // trace the code and mark all GC things it refers to. This captures any // read barriers which were skipped while compiling the script off thread. Linker linker(masm); AutoFlushICache afc("IonLink"); JitCode *code = (executionMode == SequentialExecution) ? linker.newCodeForIonScript(cx) : linker.newCode<CanGC>(cx, ION_CODE);
--- a/js/src/jit/ExecutableAllocator.cpp +++ b/js/src/jit/ExecutableAllocator.cpp @@ -57,34 +57,8 @@ ExecutableAllocator::addSizeOfCode(JS::C sizes->unused += pool->m_allocation.size - pool->m_ionCodeBytes - pool->m_baselineCodeBytes - pool->m_regexpCodeBytes - pool->m_otherCodeBytes; } } } -void -ExecutableAllocator::toggleAllCodeAsAccessible(bool accessible) -{ - if (!m_pools.initialized()) - return; - - for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) { - ExecutablePool* pool = r.front(); - pool->toggleAllCodeAsAccessible(accessible); - } -} - -bool -ExecutableAllocator::codeContains(char* address) -{ - if (!m_pools.initialized()) - return false; - - for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) { - ExecutablePool* pool = r.front(); - if (pool->codeContains(address)) - return true; - } - - return false; -}
--- a/js/src/jit/ExecutableAllocator.h +++ b/js/src/jit/ExecutableAllocator.h @@ -165,22 +165,16 @@ private: } return result; } size_t available() const { MOZ_ASSERT(m_end >= m_freePtr); return m_end - m_freePtr; } - - void toggleAllCodeAsAccessible(bool accessible); - - bool codeContains(char* address) { - return address >= m_allocation.pages && address < m_freePtr; - } }; class ExecutableAllocator { typedef void (*DestroyCallback)(void* addr, size_t size); enum ProtectionSetting { Writable, Executable }; DestroyCallback destroyCallback; public: @@ -255,18 +249,16 @@ public: if (destroyCallback) destroyCallback(pool->m_allocation.pages, pool->m_allocation.size); systemRelease(pool->m_allocation); MOZ_ASSERT(m_pools.initialized()); m_pools.remove(m_pools.lookup(pool)); // this asserts if |pool| is not in m_pools } void addSizeOfCode(JS::CodeSizes *sizes) const; - void toggleAllCodeAsAccessible(bool accessible); - bool codeContains(char* address); void setDestroyCallback(DestroyCallback destroyCallback) { this->destroyCallback = destroyCallback; } private: static size_t pageSize; static size_t largeAllocSize;
--- a/js/src/jit/ExecutableAllocatorPosix.cpp +++ b/js/src/jit/ExecutableAllocatorPosix.cpp @@ -86,23 +86,8 @@ void ExecutableAllocator::reprotectRegio // Round size up size += (pageSize - 1); size &= ~(pageSize - 1); mprotect(pageStart, size, (setting == Writable) ? PROTECTION_FLAGS_RW : PROTECTION_FLAGS_RX); } #endif -void -ExecutablePool::toggleAllCodeAsAccessible(bool accessible) -{ - char* begin = m_allocation.pages; - size_t size = m_freePtr - begin; - - if (size) { - // N.B. Some systems, like 32bit Mac OS 10.6, implicitly add PROT_EXEC - // when mprotect'ing memory with any flag other than PROT_NONE. Be - // sure to use PROT_NONE when making inaccessible. - int flags = accessible ? PROT_READ | PROT_WRITE | PROT_EXEC : PROT_NONE; - if (mprotect(begin, size, flags)) - MOZ_CRASH(); - } -}
--- a/js/src/jit/ExecutableAllocatorWin.cpp +++ b/js/src/jit/ExecutableAllocatorWin.cpp @@ -246,27 +246,11 @@ ExecutablePool::Allocation ExecutableAll return alloc; } void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc) { DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize); } -void -ExecutablePool::toggleAllCodeAsAccessible(bool accessible) -{ - char* begin = m_allocation.pages; - size_t size = m_freePtr - begin; - - if (size) { - // N.B. DEP is not on automatically in Windows XP, so be sure to use - // PAGE_NOACCESS instead of PAGE_READWRITE when making inaccessible. - DWORD oldProtect; - int flags = accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS; - if (!VirtualProtect(begin, size, flags, &oldProtect)) - MOZ_CRASH(); - } -} - #if ENABLE_ASSEMBLER_WX_EXCLUSIVE #error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform." #endif
--- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -162,17 +162,17 @@ JitRuntime::JitRuntime() argumentsRectifierReturnAddr_(nullptr), parallelArgumentsRectifier_(nullptr), invalidator_(nullptr), debugTrapHandler_(nullptr), forkJoinGetSliceStub_(nullptr), baselineDebugModeOSRHandler_(nullptr), functionWrappers_(nullptr), osrTempData_(nullptr), - ionCodeProtected_(false), + mutatingBackedgeList_(false), ionReturnOverride_(MagicValue(JS_ARG_POISON)), jitcodeGlobalTable_(nullptr) { } JitRuntime::~JitRuntime() { js_delete(functionWrappers_); @@ -186,17 +186,16 @@ JitRuntime::~JitRuntime() MOZ_ASSERT_IF(jitcodeGlobalTable_, jitcodeGlobalTable_->empty()); js_delete(jitcodeGlobalTable_); } bool JitRuntime::initialize(JSContext *cx) { MOZ_ASSERT(cx->runtime()->currentThreadHasExclusiveAccess()); - MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock()); AutoCompartment ac(cx, cx->atomsCompartment()); IonContext ictx(cx, nullptr); execAlloc_ = cx->runtime()->getExecAlloc(cx); if (!execAlloc_) return false; @@ -361,151 +360,42 @@ JitRuntime::freeOsrTempData() { js_free(osrTempData_); osrTempData_ = nullptr; } ExecutableAllocator * JitRuntime::createIonAlloc(JSContext *cx) { - MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock()); - ionAlloc_ = js_new<ExecutableAllocator>(); if (!ionAlloc_) js_ReportOutOfMemory(cx); return ionAlloc_; } void -JitRuntime::ensureIonCodeProtected(JSRuntime *rt) -{ - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - - if (!rt->signalHandlersInstalled() || ionCodeProtected_ || !ionAlloc_) - return; - - // Protect all Ion code in the runtime to trigger an access violation the - // next time any of it runs on the main thread. - ionAlloc_->toggleAllCodeAsAccessible(false); - ionCodeProtected_ = true; -} - -bool -JitRuntime::handleAccessViolation(JSRuntime *rt, void *faultingAddress) -{ - if (!rt->signalHandlersInstalled() || !ionAlloc_ || !ionAlloc_->codeContains((char *) faultingAddress)) - return false; - - // All places where the interrupt lock is taken must either ensure that Ion - // code memory won't be accessed within, or call ensureIonCodeAccessible to - // render the memory safe for accessing. Otherwise taking the lock below - // will deadlock the process. - MOZ_ASSERT(!rt->currentThreadOwnsInterruptLock()); - - // Taking this lock is necessary to prevent the interrupting thread from marking - // the memory as inaccessible while we are patching backedges. This will cause us - // to SEGV while still inside the signal handler, and the process will terminate. - JSRuntime::AutoLockForInterrupt lock(rt); - - // Ion code in the runtime faulted after it was made inaccessible. Reset - // the code privileges and patch all loop backedges to perform an interrupt - // check instead. - ensureIonCodeAccessible(rt); - return true; -} - -void -JitRuntime::ensureIonCodeAccessible(JSRuntime *rt) -{ - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - - // This can only be called on the main thread and while handling signals, - // which happens on a separate thread in OS X. -#ifndef XP_MACOSX - MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); -#endif - - if (ionCodeProtected_) { - ionAlloc_->toggleAllCodeAsAccessible(true); - ionCodeProtected_ = false; - } - - if (rt->hasPendingInterrupt()) { - // The interrupt handler needs to be invoked by this thread, but we may - // be inside a signal handler and have no idea what is above us on the - // stack (probably we are executing Ion code at an arbitrary point, but - // we could be elsewhere, say repatching a jump for an IonCache). - // Patch all backedges in the runtime so they will invoke the interrupt - // handler the next time they execute. - patchIonBackedges(rt, BackedgeInterruptCheck); - } -} - -void JitRuntime::patchIonBackedges(JSRuntime *rt, BackedgeTarget target) { -#ifndef XP_MACOSX - MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); -#endif + MOZ_ASSERT_IF(target == BackedgeLoopHeader, mutatingBackedgeList_); + MOZ_ASSERT_IF(target == BackedgeInterruptCheck, !mutatingBackedgeList_); // Patch all loop backedges in Ion code so that they either jump to the // normal loop header or to an interrupt handler each time they run. for (InlineListIterator<PatchableBackedge> iter(backedgeList_.begin()); iter != backedgeList_.end(); iter++) { PatchableBackedge *patchableBackedge = *iter; if (target == BackedgeLoopHeader) PatchBackedge(patchableBackedge->backedge, patchableBackedge->loopHeader, target); else PatchBackedge(patchableBackedge->backedge, patchableBackedge->interruptCheck, target); } } -void -jit::RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode) -{ - JitRuntime *jitRuntime = rt->jitRuntime(); - if (!jitRuntime) - return; - - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - - // The mechanism for interrupting normal ion code varies depending on how - // the interrupt is being requested. - switch (mode) { - case JSRuntime::RequestInterruptMainThread: - // When requesting an interrupt from the main thread, Ion loop - // backedges can be patched directly. Make sure we don't segv while - // patching the backedges, to avoid deadlocking inside the signal - // handler. - MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); - jitRuntime->ensureIonCodeAccessible(rt); - break; - - case JSRuntime::RequestInterruptAnyThread: - // When requesting an interrupt from off the main thread, protect - // Ion code memory so that the main thread will fault and enter a - // signal handler when trying to execute the code. The signal - // handler will unprotect the code and patch loop backedges so - // that the interrupt handler is invoked afterwards. - jitRuntime->ensureIonCodeProtected(rt); - break; - - case JSRuntime::RequestInterruptAnyThreadDontStopIon: - case JSRuntime::RequestInterruptAnyThreadForkJoin: - // The caller does not require Ion code to be interrupted. - // Nothing more needs to be done. - break; - - default: - MOZ_CRASH("Bad interrupt mode"); - } -} - JitCompartment::JitCompartment() : stubCodes_(nullptr), baselineCallReturnAddr_(nullptr), baselineGetPropReturnAddr_(nullptr), baselineSetPropReturnAddr_(nullptr), stringConcatStub_(nullptr), parallelStringConcatStub_(nullptr), regExpExecStub_(nullptr), @@ -865,30 +755,26 @@ JitCode::trace(JSTracer *trc) CompactBufferReader reader(start, start + dataRelocTableBytes_); MacroAssembler::TraceDataRelocations(trc, this, reader); } } void JitCode::finalize(FreeOp *fop) { - // Make sure this can't race with an interrupting thread, which may try - // to read the contents of the pool we are releasing references in. - MOZ_ASSERT(fop->runtime()->currentThreadOwnsInterruptLock()); - // If this jitcode has a bytecode map, de-register it. if (hasBytecodeMap_) { MOZ_ASSERT(fop->runtime()->jitRuntime()->hasJitcodeGlobalTable()); fop->runtime()->jitRuntime()->getJitcodeGlobalTable()->removeEntry(raw()); } // Buffer can be freed at any time hereafter. Catch use-after-free bugs. // Don't do this if the Ion code is protected, as the signal handler will // deadlock trying to reacquire the interrupt lock. - if (fop->runtime()->jitRuntime() && !fop->runtime()->jitRuntime()->ionCodeProtected()) + if (fop->runtime()->jitRuntime()) memset(code_, JS_SWEPT_CODE_PATTERN, bufferSize_); code_ = nullptr; // Code buffers are stored inside JSC pools. // Pools are refcounted. Releasing the pool may free it. if (pool_) { // Horrible hack: if we are using perf integration, we don't // want to reuse code addresses, so we just leak the memory instead. @@ -1139,16 +1025,19 @@ IonScript::copyCallTargetEntries(JSScrip callTargetList()[i] = callTargets[i]; } void IonScript::copyPatchableBackedges(JSContext *cx, JitCode *code, PatchableBackedgeInfo *backedges, MacroAssembler &masm) { + JitRuntime *jrt = cx->runtime()->jitRuntime(); + JitRuntime::AutoMutateBackedges amb(jrt); + for (size_t i = 0; i < backedgeEntries_; i++) { PatchableBackedgeInfo &info = backedges[i]; PatchableBackedge *patchableBackedge = &backedgeList()[i]; // Convert to actual offsets for the benefit of the ARM backend. info.backedge.fixup(&masm); uint32_t loopHeaderOffset = masm.actualOffset(info.loopHeader->offset()); uint32_t interruptCheckOffset = masm.actualOffset(info.interruptCheck->offset()); @@ -1162,17 +1051,17 @@ IonScript::copyPatchableBackedges(JSCont // whether an interrupt is currently desired, matching the targets // established by ensureIonCodeAccessible() above. We don't handle the // interrupt immediately as the interrupt lock is held here. if (cx->runtime()->hasPendingInterrupt()) PatchBackedge(backedge, interruptCheck, JitRuntime::BackedgeInterruptCheck); else PatchBackedge(backedge, loopHeader, JitRuntime::BackedgeLoopHeader); - cx->runtime()->jitRuntime()->addPatchableBackedge(patchableBackedge); + jrt->addPatchableBackedge(patchableBackedge); } } void IonScript::copySafepointIndices(const SafepointIndex *si, MacroAssembler &masm) { // Jumps in the caches reflect the offset of those jumps in the compiled // code, not the absolute positions of the jumps. Update according to the @@ -1353,21 +1242,20 @@ IonScript::unlinkFromRuntime(FreeOp *fop fop->delete_(dependentAsmJSModules); dependentAsmJSModules = nullptr; } // The writes to the executable buffer below may clobber backedge jumps, so // make sure that those backedges are unlinked from the runtime and not // reclobbered with garbage if an interrupt is requested. - JSRuntime *rt = fop->runtime(); - for (size_t i = 0; i < backedgeEntries_; i++) { - PatchableBackedge *backedge = &backedgeList()[i]; - rt->jitRuntime()->removePatchableBackedge(backedge); - } + JitRuntime *jrt = fop->runtime()->jitRuntime(); + JitRuntime::AutoMutateBackedges amb(jrt); + for (size_t i = 0; i < backedgeEntries_; i++) + jrt->removePatchableBackedge(&backedgeList()[i]); // Clear the list of backedges, so that this method is idempotent. It is // called during destruction, and may be additionally called when the // script is invalidated. backedgeEntries_ = 0; } void
--- a/js/src/jit/Ion.h +++ b/js/src/jit/Ion.h @@ -196,18 +196,16 @@ NumLocalsAndArgs(JSScript *script) void ForbidCompilation(JSContext *cx, JSScript *script); void ForbidCompilation(JSContext *cx, JSScript *script, ExecutionMode mode); void PurgeCaches(JSScript *script); size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf); void DestroyIonScripts(FreeOp *fop, JSScript *script); void TraceIonScripts(JSTracer* trc, JSScript *script); -void RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode); - bool RematerializeAllFrames(JSContext *cx, JSCompartment *comp); bool UpdateForDebugMode(JSContext *maybecx, JSCompartment *comp, AutoDebugModeInvalidation &invalidate); bool JitSupportsFloatingPoint(); bool JitSupportsSimd(); } // namespace jit
--- a/js/src/jit/IonLinker.h +++ b/js/src/jit/IonLinker.h @@ -77,20 +77,16 @@ class Linker } template <AllowGC allowGC> JitCode *newCode(JSContext *cx, CodeKind kind) { return newCode<allowGC>(cx, cx->runtime()->jitRuntime()->execAlloc(), kind); } JitCode *newCodeForIonScript(JSContext *cx) { - // The caller must lock the runtime against interrupt requests, as the - // thread requesting an interrupt may use the executable allocator below. - MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock()); - ExecutableAllocator *alloc = cx->runtime()->jitRuntime()->getIonAlloc(cx); if (!alloc) return nullptr; return newCode<CanGC>(cx, alloc, ION_CODE); } };
--- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -212,22 +212,21 @@ class JitRuntime typedef WeakCache<const VMFunction *, JitCode *> VMWrapperMap; VMWrapperMap *functionWrappers_; // Buffer for OSR from baseline to Ion. To avoid holding on to this for // too long, it's also freed in JitCompartment::mark and in EnterBaseline // (after returning from JIT code). uint8_t *osrTempData_; - // Whether all Ion code in the runtime is protected, and will fault if it - // is accessed. - bool ionCodeProtected_; - - // If signal handlers are installed, this contains all loop backedges for - // IonScripts in the runtime. + // List of all backedges in all Ion code. The backedge edge list is accessed + // asynchronously when the main thread is paused and mutatingBackedgeList_ + // is false. Thus, the list must only be mutated while mutatingBackedgeList_ + // is true. + volatile bool mutatingBackedgeList_; InlineList<PatchableBackedge> backedgeList_; // In certain cases, we want to optimize certain opcodes to typed instructions, // to avoid carrying an extra register to feed into an unbox. Unfortunately, // that's not always possible. For example, a GetPropertyCacheT could return a // typed double, but if it takes its out-of-line path, it could return an // object, and trigger invalidation. The invalidation bailout will consider the // return value to be a double, and create a garbage Value. @@ -269,53 +268,59 @@ class JitRuntime uint8_t *allocateOsrTempData(size_t size); void freeOsrTempData(); static void Mark(JSTracer *trc); ExecutableAllocator *execAlloc() const { return execAlloc_; } - ExecutableAllocator *getIonAlloc(JSContext *cx) { - MOZ_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock()); return ionAlloc_ ? ionAlloc_ : createIonAlloc(cx); } - ExecutableAllocator *ionAlloc(JSRuntime *rt) { - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); return ionAlloc_; } - bool hasIonAlloc() const { return !!ionAlloc_; } - bool ionCodeProtected() { - return ionCodeProtected_; + class AutoMutateBackedges + { + JitRuntime *jrt_; + public: + AutoMutateBackedges(JitRuntime *jrt) : jrt_(jrt) { + MOZ_ASSERT(!jrt->mutatingBackedgeList_); + jrt->mutatingBackedgeList_ = true; + } + ~AutoMutateBackedges() { + MOZ_ASSERT(jrt_->mutatingBackedgeList_); + jrt_->mutatingBackedgeList_ = false; + } + }; + + bool mutatingBackedgeList() const { + return mutatingBackedgeList_; } - void addPatchableBackedge(PatchableBackedge *backedge) { + MOZ_ASSERT(mutatingBackedgeList_); backedgeList_.pushFront(backedge); } void removePatchableBackedge(PatchableBackedge *backedge) { + MOZ_ASSERT(mutatingBackedgeList_); backedgeList_.remove(backedge); } enum BackedgeTarget { BackedgeLoopHeader, BackedgeInterruptCheck }; - void ensureIonCodeProtected(JSRuntime *rt); - void ensureIonCodeAccessible(JSRuntime *rt); void patchIonBackedges(JSRuntime *rt, BackedgeTarget target); - bool handleAccessViolation(JSRuntime *rt, void *faultingAddress); - JitCode *getVMWrapper(const VMFunction &f) const; JitCode *debugTrapHandler(JSContext *cx); JitCode *getBaselineDebugModeOSRHandler(JSContext *cx); void *getBaselineDebugModeOSRHandlerAddress(JSContext *cx, bool popFrameReg); JitCode *getGenericBailoutHandler(ExecutionMode mode) const { switch (mode) { case SequentialExecution: return bailoutHandler_;
--- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -517,25 +517,21 @@ SetProperty(JSContext *cx, HandleObject return JSObject::setGeneric(cx, obj, obj, id, &v, strict); } bool InterruptCheck(JSContext *cx) { gc::MaybeVerifyBarriers(cx); - // Fix loop backedges so that they do not invoke the interrupt again. - // No lock is held here and it's possible we could segv in the middle here - // and end up with a state where some fraction of the backedges point to - // the interrupt handler and some don't. This is ok since the interrupt - // is definitely about to be handled; if there are still backedges - // afterwards which point to the interrupt handler, the next time they are - // taken the backedges will just be reset again. - cx->runtime()->jitRuntime()->patchIonBackedges(cx->runtime(), - JitRuntime::BackedgeLoopHeader); + { + JitRuntime *jrt = cx->runtime()->jitRuntime(); + JitRuntime::AutoMutateBackedges amb(jrt); + jrt->patchIonBackedges(cx->runtime(), JitRuntime::BackedgeLoopHeader); + } return CheckForInterrupt(cx); } void * MallocWrapper(JSRuntime *rt, size_t nbytes) { return rt->pod_malloc<uint8_t>(nbytes);
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5102,17 +5102,17 @@ JS_PUBLIC_API(JSInterruptCallback) JS_GetInterruptCallback(JSRuntime *rt) { return rt->interruptCallback; } JS_PUBLIC_API(void) JS_RequestInterruptCallback(JSRuntime *rt) { - rt->requestInterrupt(JSRuntime::RequestInterruptAnyThread); + rt->requestInterrupt(JSRuntime::RequestInterruptUrgent); } JS_PUBLIC_API(bool) JS_IsRunning(JSContext *cx) { return cx->currentlyRunning(); }
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -288,17 +288,16 @@ struct ThreadSafeContext : ContextFriend const JS::AsmJSCacheOps &asmJSCacheOps() { return runtime_->asmJSCacheOps; } PropertyName *emptyString() { return runtime_->emptyString; } FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); } void *runtimeAddressForJit() { return runtime_; } void *runtimeAddressOfInterruptUint32() { return runtime_->addressOfInterruptUint32(); } void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; } void *stackLimitAddressForJitCode(StackKind kind); size_t gcSystemPageSize() { return gc::SystemPageSize(); } - bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); } bool canUseSignalHandlers() const { return runtime_->canUseSignalHandlers(); } bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; } bool jitSupportsSimd() const { return runtime_->jitSupportsSimd; } // Thread local data that may be accessed freely. DtoaState *dtoaState() { return perThreadData->dtoaState; }
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -28,16 +28,17 @@ #include "jsatominlines.h" #include "jsfuninlines.h" #include "jsgcinlines.h" #include "jsinferinlines.h" #include "jsobjinlines.h" using namespace js; using namespace js::gc; +using namespace js::jit; using mozilla::DebugOnly; JSCompartment::JSCompartment(Zone *zone, const JS::CompartmentOptions &options = JS::CompartmentOptions()) : options_(options), zone_(zone), runtime_(zone->runtimeFromMainThread()), principals(nullptr), @@ -120,27 +121,27 @@ JSCompartment::init(JSContext *cx) jit::JitRuntime * JSRuntime::createJitRuntime(JSContext *cx) { // The shared stubs are created in the atoms compartment, which may be // accessed by other threads with an exclusive context. AutoLockForExclusiveAccess atomsLock(cx); - // The runtime will only be created on its owning thread, but reads of a - // runtime's jitRuntime() can occur when another thread is requesting an - // interrupt. - AutoLockForInterrupt lock(this); - MOZ_ASSERT(!jitRuntime_); - jitRuntime_ = cx->new_<jit::JitRuntime>(); + jit::JitRuntime *jrt = cx->new_<jit::JitRuntime>(); + if (!jrt) + return nullptr; - if (!jitRuntime_) - return nullptr; + // Protect jitRuntime_ from being observed (by InterruptRunningJitCode) + // while it is being initialized. Unfortunately, initialization depends on + // jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_. + JitRuntime::AutoMutateBackedges amb(jrt); + jitRuntime_ = jrt; if (!jitRuntime_->initialize(cx)) { js_delete(jitRuntime_); jitRuntime_ = nullptr; JSCompartment *comp = cx->runtime()->atomsCompartment(); if (comp->jitCompartment_) { js_delete(comp->jitCompartment_);
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -640,22 +640,17 @@ FinalizeArenas(FreeOp *fop, return FinalizeTypedArenas<JSString>(fop, src, dest, thingKind, budget, keepArenas); case FINALIZE_FAT_INLINE_STRING: return FinalizeTypedArenas<JSFatInlineString>(fop, src, dest, thingKind, budget, keepArenas); case FINALIZE_EXTERNAL_STRING: return FinalizeTypedArenas<JSExternalString>(fop, src, dest, thingKind, budget, keepArenas); case FINALIZE_SYMBOL: return FinalizeTypedArenas<JS::Symbol>(fop, src, dest, thingKind, budget, keepArenas); case FINALIZE_JITCODE: - { - // JitCode finalization may release references on an executable - // allocator that is accessed when requesting interrupts. - JSRuntime::AutoLockForInterrupt lock(fop->runtime()); return FinalizeTypedArenas<jit::JitCode>(fop, src, dest, thingKind, budget, keepArenas); - } default: MOZ_CRASH("Invalid alloc kind"); } } static inline Chunk * AllocChunk(JSRuntime *rt) { @@ -1058,17 +1053,17 @@ class js::gc::AutoMaybeStartBackgroundAl MOZ_GUARD_OBJECT_NOTIFIER_INIT; } void tryToStartBackgroundAllocation(JSRuntime *rt) { runtime = rt; } ~AutoMaybeStartBackgroundAllocation() { - if (runtime && !runtime->currentThreadOwnsInterruptLock()) + if (runtime) runtime->gc.startBackgroundAllocTaskIfIdle(); } }; Chunk * GCRuntime::pickChunk(const AutoLockGC &lock, AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation) { @@ -2995,29 +2990,29 @@ js::MarkCompartmentActive(InterpreterFra void GCRuntime::requestMajorGC(JS::gcreason::Reason reason) { if (majorGCRequested) return; majorGCRequested = true; majorGCTriggerReason = reason; - rt->requestInterrupt(JSRuntime::RequestInterruptMainThread); + rt->requestInterrupt(JSRuntime::RequestInterruptUrgent); } void GCRuntime::requestMinorGC(JS::gcreason::Reason reason) { MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); if (minorGCRequested) return; minorGCRequested = true; minorGCTriggerReason = reason; - rt->requestInterrupt(JSRuntime::RequestInterruptMainThread); + rt->requestInterrupt(JSRuntime::RequestInterruptUrgent); } bool GCRuntime::triggerGC(JS::gcreason::Reason reason) { /* Wait till end of parallel section to trigger GC. */ if (InParallelSection()) { ForkJoinContext::current()->requestGC(reason); @@ -3026,20 +3021,16 @@ GCRuntime::triggerGC(JS::gcreason::Reaso /* * Don't trigger GCs if this is being called off the main thread from * onTooMuchMalloc(). */ if (!CurrentThreadCanAccessRuntime(rt)) return false; - /* Don't trigger GCs when allocating under the interrupt callback lock. */ - if (rt->currentThreadOwnsInterruptLock()) - return false; - /* GC is already running. */ if (rt->isHeapCollecting()) return false; JS::PrepareForFullGC(rt); requestMajorGC(reason); return true; } @@ -3089,20 +3080,16 @@ GCRuntime::triggerZoneGC(Zone *zone, JS: ForkJoinContext::current()->requestZoneGC(zone, reason); return true; } /* Zones in use by a thread with an exclusive context can't be collected. */ if (zone->usedByExclusiveThread) return false; - /* Don't trigger GCs when allocating under the interrupt callback lock. */ - if (rt->currentThreadOwnsInterruptLock()) - return false; - /* GC is already running. */ if (rt->isHeapCollecting()) return false; #ifdef JS_GC_ZEAL if (zealMode == ZealAllocValue) { triggerGC(reason); return true; @@ -5339,20 +5326,18 @@ GCRuntime::endSweepPhase(bool lastGC) */ if (isFull) SweepScriptData(rt); /* Clear out any small pools that we're hanging on to. */ if (jit::ExecutableAllocator *execAlloc = rt->maybeExecAlloc()) execAlloc->purge(); - if (rt->jitRuntime() && rt->jitRuntime()->hasIonAlloc()) { - JSRuntime::AutoLockForInterrupt lock(rt); + if (rt->jitRuntime() && rt->jitRuntime()->hasIonAlloc()) rt->jitRuntime()->ionAlloc(rt)->purge(); - } /* * This removes compartments from rt->compartment, so we do it last to make * sure we don't miss sweeping any compartments. */ if (!lastGC) sweepZones(&fop, lastGC); }
--- a/js/src/vm/ForkJoin.cpp +++ b/js/src/vm/ForkJoin.cpp @@ -1662,19 +1662,17 @@ ForkJoinShared::setAbortFlagDueToInterru void ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal) { AutoLockMonitor lock(*this); abort_ = true; fatal_ = fatal_ || fatal; - // Note: The ForkJoin trigger here avoids the expensive memory protection needed to - // interrupt Ion code compiled for sequential execution. - cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptAnyThreadForkJoin); + cx_->runtime()->requestInterrupt(JSRuntime::RequestInterruptCanWait); } void ForkJoinShared::requestGC(JS::gcreason::Reason reason) { AutoLockMonitor lock(*this); gcZone_ = nullptr;
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -1060,17 +1060,17 @@ HelperThread::handleIonWorkload() FinishOffThreadIonCompile(ionBuilder); ionBuilder = nullptr; pause = false; // Ping the main thread so that the compiled code can be incorporated // at the next interrupt callback. Don't interrupt Ion code for this, as // this incorporation can be delayed indefinitely without affecting // performance as long as the main thread is actually executing Ion code. - rt->requestInterrupt(JSRuntime::RequestInterruptAnyThreadDontStopIon); + rt->requestInterrupt(JSRuntime::RequestInterruptCanWait); // Notify the main thread in case it is waiting for the compilation to finish. HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER); // When finishing Ion compilation jobs, we can start unpausing compilation // threads that were paused to restrict the number of active compilations. // Only unpause one at a time, to make sure we don't exceed the restriction. // Since threads are currently only paused for Ion compilations, this
--- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -22,16 +22,17 @@ #include "jsatom.h" #include "jsdtoa.h" #include "jsgc.h" #include "jsmath.h" #include "jsnativestack.h" #include "jsobj.h" #include "jsscript.h" #include "jswatchpoint.h" +#include "jswin.h" #include "jswrapper.h" #include "asmjs/AsmJSSignalHandlers.h" #include "jit/arm/Simulator-arm.h" #include "jit/JitCompartment.h" #include "jit/mips/Simulator-mips.h" #include "jit/PcScriptCache.h" #include "js/MemoryMetrics.h" @@ -135,28 +136,27 @@ JSRuntime::JSRuntime(JSRuntime *parentRu #endif ), mainThread(this), parentRuntime(parentRuntime), interrupt_(false), interruptPar_(false), handlingSignal(false), interruptCallback(nullptr), - interruptLock(nullptr), - interruptLockOwner(nullptr), exclusiveAccessLock(nullptr), exclusiveAccessOwner(nullptr), mainThreadHasExclusiveAccess(false), numExclusiveThreads(0), numCompartments(0), localeCallbacks(nullptr), defaultLocale(nullptr), defaultVersion_(JSVERSION_DEFAULT), futexAPI_(nullptr), ownerThread_(nullptr), + ownerThreadNative_(0), tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), execAlloc_(nullptr), jitRuntime_(nullptr), selfHostingGlobal_(nullptr), nativeStackBase(GetNativeStackBase()), cxCallback(nullptr), destroyCompartmentCallback(nullptr), destroyZoneCallback(nullptr), @@ -251,19 +251,29 @@ SignalBasedTriggersDisabled() return !!getenv("JS_DISABLE_SLOW_SCRIPT_SIGNALS") || !!getenv("JS_NO_SIGNALS"); } bool JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) { ownerThread_ = PR_GetCurrentThread(); - interruptLock = PR_NewLock(); - if (!interruptLock) - return false; + // Get a platform-native handle for the owner thread, used by + // js::InterruptRunningJitCode to halt the runtime's main thread. +#ifdef XP_WIN + size_t openFlags = THREAD_GET_CONTEXT | THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME; + HANDLE self = OpenThread(openFlags, false, GetCurrentThreadId()); + if (!self) + MOZ_CRASH("Unable to open thread handle"); + static_assert(sizeof(HANDLE) <= sizeof(ownerThreadNative_), "need bigger field"); + ownerThreadNative_ = (size_t)self; +#else + static_assert(sizeof(pthread_t) <= sizeof(ownerThreadNative_), "need bigger field"); + ownerThreadNative_ = (size_t)pthread_self(); +#endif exclusiveAccessLock = PR_NewLock(); if (!exclusiveAccessLock) return false; if (!mainThread.init()) return false; @@ -320,17 +330,17 @@ JSRuntime::init(uint32_t maxbytes, uint3 simulatorRuntime_ = js::jit::CreateSimulatorRuntime(); if (!simulatorRuntime_) return false; #endif jitSupportsFloatingPoint = js::jit::JitSupportsFloatingPoint(); jitSupportsSimd = js::jit::JitSupportsSimd(); - signalHandlersInstalled_ = EnsureAsmJSSignalHandlersInstalled(this); + signalHandlersInstalled_ = EnsureSignalHandlersInstalled(this); canUseSignalHandlers_ = signalHandlersInstalled_ && !SignalBasedTriggersDisabled(); if (!spsProfiler.init()) return false; return true; } @@ -386,20 +396,16 @@ JSRuntime::~JSRuntime() MOZ_ASSERT(!exclusiveAccessOwner); if (exclusiveAccessLock) PR_DestroyLock(exclusiveAccessLock); // Avoid bogus asserts during teardown. MOZ_ASSERT(!numExclusiveThreads); mainThreadHasExclusiveAccess = true; - MOZ_ASSERT(!interruptLockOwner); - if (interruptLock) - PR_DestroyLock(interruptLock); - /* * Even though all objects in the compartment are dead, we may have keep * some filenames around because of gcKeepAtoms. */ FreeScriptData(this); #ifdef DEBUG /* Don't hurt everyone in leaky ol' Mozilla with a fatal MOZ_ASSERT! */ @@ -439,16 +445,21 @@ JSRuntime::~JSRuntime() #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) js::jit::DestroySimulatorRuntime(simulatorRuntime_); #endif DebugOnly<size_t> oldCount = liveRuntimesCount--; MOZ_ASSERT(oldCount > 0); js::TlsPerThreadData.set(nullptr); + +#ifdef XP_WIN + if (ownerThreadNative_) + CloseHandle((HANDLE)ownerThreadNative_); +#endif } void NewObjectCache::clearNurseryObjects(JSRuntime *rt) { #ifdef JSGC_GENERATIONAL for (unsigned i = 0; i < mozilla::ArrayLength(entries); ++i) { Entry &e = entries[i]; @@ -495,23 +506,19 @@ JSRuntime::addSizeOfIncludingThis(mozill rtSizes->compressedSourceSet += compressedSourceSet.sizeOfExcludingThis(mallocSizeOf); rtSizes->scriptData += scriptDataTable().sizeOfExcludingThis(mallocSizeOf); for (ScriptDataTable::Range r = scriptDataTable().all(); !r.empty(); r.popFront()) rtSizes->scriptData += mallocSizeOf(r.front()); if (execAlloc_) execAlloc_->addSizeOfCode(&rtSizes->code); - { - AutoLockForInterrupt lock(this); - if (jitRuntime()) { - if (jit::ExecutableAllocator *ionAlloc = jitRuntime()->ionAlloc(this)) - ionAlloc->addSizeOfCode(&rtSizes->code); - } - } + + if (jitRuntime() && jitRuntime()->ionAlloc(this)) + jitRuntime()->ionAlloc(this)->addSizeOfCode(&rtSizes->code); rtSizes->gc.marker += gc.marker.sizeOfExcludingThis(mallocSizeOf); #ifdef JSGC_GENERATIONAL rtSizes->gc.nurseryCommitted += gc.nursery.sizeOfHeapCommitted(); rtSizes->gc.nurseryDecommitted += gc.nursery.sizeOfHeapDecommitted(); rtSizes->gc.nurseryHugeSlots += gc.nursery.sizeOfHugeSlots(mallocSizeOf); gc.storeBuffer.addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc); #endif @@ -606,21 +613,18 @@ PerThreadData::initJitStackLimitPar(uint void JSRuntime::requestInterrupt(InterruptMode mode) { interrupt_ = true; interruptPar_ = true; mainThread.jitStackLimit_ = UINTPTR_MAX; - if (canUseSignalHandlers()) { - AutoLockForInterrupt lock(this); - RequestInterruptForAsmJSCode(this, mode); - jit::RequestInterruptForIonCode(this, mode); - } + if (mode == JSRuntime::RequestInterruptUrgent) + InterruptRunningJitCode(this); } bool JSRuntime::handleInterrupt(JSContext *cx) { MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); if (interrupt_ || mainThread.jitStackLimit_ == UINTPTR_MAX) { interrupt_ = false; @@ -831,18 +835,16 @@ JSRuntime::assertCanLock(RuntimeLock whi // In the switch below, each case falls through to the one below it. None // of the runtime locks are reentrant, and when multiple locks are acquired // it must be done in the order below. switch (which) { case ExclusiveAccessLock: MOZ_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread()); case HelperThreadStateLock: MOZ_ASSERT(!HelperThreadState().isLocked()); - case InterruptLock: - MOZ_ASSERT(!currentThreadOwnsInterruptLock()); case GCLock: gc.assertCanLock(); break; default: MOZ_CRASH(); } }
--- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -447,17 +447,16 @@ AtomStateOffsetToName(const JSAtomState } // There are several coarse locks in the enum below. These may be either // per-runtime or per-process. When acquiring more than one of these locks, // the acquisition must be done in the order below to avoid deadlocks. enum RuntimeLock { ExclusiveAccessLock, HelperThreadStateLock, - InterruptLock, GCLock }; #ifdef DEBUG void AssertCurrentThreadCanLock(RuntimeLock which); #else inline void AssertCurrentThreadCanLock(RuntimeLock which) {} #endif @@ -536,24 +535,16 @@ class PerThreadData : public PerThreadDa // Information about the heap allocated backtrack stack used by RegExp JIT code. irregexp::RegExpStack regexpStack; #ifdef JS_TRACE_LOGGING TraceLogger *traceLogger; #endif - /* - * asm.js maintains a stack of AsmJSModule activations (see AsmJS.h). This - * stack is used by JSRuntime::requestInterrupt to stop long-running asm.js - * without requiring dynamic polling operations in the generated - * code. Since requestInterrupt may run on a separate thread than the - * JSRuntime's owner thread all reads/writes must be synchronized (by - * rt->interruptLock). - */ private: friend class js::Activation; friend class js::ActivationIterator; friend class js::jit::JitActivation; friend class js::AsmJSActivation; #ifdef DEBUG friend void js::AssertCurrentThreadCanLock(RuntimeLock which); #endif @@ -561,21 +552,21 @@ class PerThreadData : public PerThreadDa /* * Points to the most recent activation running on the thread. * See Activation comment in vm/Stack.h. */ js::Activation *activation_; /* * Points to the most recent profiling activation running on the - * thread. Protected by rt->interruptLock. + * thread. */ js::Activation * volatile profilingActivation_; - /* See AsmJSActivation comment. Protected by rt->interruptLock. */ + /* See AsmJSActivation comment. */ js::AsmJSActivation * volatile asmJSActivationStack_; /* Pointer to the current AutoFlushICache. */ js::jit::AutoFlushICache *autoFlushICache_; #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) js::jit::Simulator *simulator_; uintptr_t simulatorStackLimit_; @@ -709,20 +700,18 @@ struct JSRuntime : public JS::shadow::Ru JSRuntime *parentRuntime; private: mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_; mozilla::Atomic<uint32_t, mozilla::Relaxed> interruptPar_; public: enum InterruptMode { - RequestInterruptMainThread, - RequestInterruptAnyThread, - RequestInterruptAnyThreadDontStopIon, - RequestInterruptAnyThreadForkJoin + RequestInterruptUrgent, + RequestInterruptCanWait }; // Any thread can call requestInterrupt() to request that the main JS thread // stop running and call the interrupt callback (allowing the interrupt // callback to halt execution). To stop the main JS thread, requestInterrupt // sets two fields: interrupt_ (set to true) and jitStackLimit_ (set to // UINTPTR_MAX). The JS engine must continually poll one of these fields // and call handleInterrupt if either field has the interrupt value. (The @@ -767,47 +756,16 @@ struct JSRuntime : public JS::shadow::Ru #ifdef DEBUG void assertCanLock(js::RuntimeLock which); #else void assertCanLock(js::RuntimeLock which) {} #endif private: /* - * Lock taken when triggering an interrupt from another thread. - * Protects all data that is touched in this process. - */ - PRLock *interruptLock; - PRThread *interruptLockOwner; - - public: - class AutoLockForInterrupt { - JSRuntime *rt; - public: - explicit AutoLockForInterrupt(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - rt->assertCanLock(js::InterruptLock); - PR_Lock(rt->interruptLock); - rt->interruptLockOwner = PR_GetCurrentThread(); - } - ~AutoLockForInterrupt() { - MOZ_ASSERT(rt->currentThreadOwnsInterruptLock()); - rt->interruptLockOwner = nullptr; - PR_Unlock(rt->interruptLock); - } - - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER - }; - - bool currentThreadOwnsInterruptLock() { - return interruptLockOwner == PR_GetCurrentThread(); - } - - private: - /* * Lock taken when using per-runtime or per-zone data that could otherwise * be accessed simultaneously by both the main thread and another thread * with an ExclusiveContext. * * Locking this only occurs if there is actually a thread other than the * main thread with an ExclusiveContext which could access such data. */ PRLock *exclusiveAccessLock; @@ -847,19 +805,24 @@ struct JSRuntime : public JS::shadow::Ru JSVersion defaultVersion_; /* Futex API, if installed */ JS::PerRuntimeFutexAPI *futexAPI_; private: /* See comment for JS_AbortIfWrongThread in jsapi.h. */ void *ownerThread_; + size_t ownerThreadNative_; friend bool js::CurrentThreadCanAccessRuntime(JSRuntime *rt); public: + size_t ownerThreadNative() const { + return ownerThreadNative_; + } + /* Temporary arena pool used while compiling and decompiling. */ static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024; js::LifoAlloc tempLifoAlloc; private: /* * Both of these allocators are used for regular expression code which is shared at the * thread-data level. @@ -1084,26 +1047,25 @@ struct JSRuntime : public JS::shadow::Ru /* Client opaque pointers */ void *data; #ifdef XP_MACOSX js::AsmJSMachExceptionHandler asmJSMachExceptionHandler; #endif private: - // Whether asm.js signal handlers have been installed and can be used for - // performing interrupt checks in loops. + // Whether EnsureSignalHandlersInstalled succeeded in installing all the + // relevant handlers for this platform. bool signalHandlersInstalled_; + // Whether we should use them or they have been disabled for making // debugging easier. If signal handlers aren't installed, it is set to false. bool canUseSignalHandlers_; + public: - bool signalHandlersInstalled() const { - return signalHandlersInstalled_; - } bool canUseSignalHandlers() const { return canUseSignalHandlers_; } void setCanUseSignalHandlers(bool enable) { canUseSignalHandlers_ = signalHandlersInstalled_ && enable; } private:
--- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -1553,21 +1553,17 @@ AsmJSActivation::AsmJSActivation(JSConte 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; - } + cx->mainThread().asmJSActivationStack_ = this; // Now that the AsmJSActivation is fully initialized, make it visible to // asynchronous profiling. registerProfiling(); } AsmJSActivation::~AsmJSActivation() { @@ -1580,17 +1576,16 @@ AsmJSActivation::~AsmJSActivation() MOZ_ASSERT(fp_ == nullptr); MOZ_ASSERT(module_.activation() == this); module_.activation() = prevAsmJSForModule_; JSContext *cx = cx_->asJSContext(); MOZ_ASSERT(cx->mainThread().asmJSActivationStack_ == this); - JSRuntime::AutoLockForInterrupt lock(cx->runtime()); cx->mainThread().asmJSActivationStack_ = prevAsmJS_; } InterpreterFrameIterator & InterpreterFrameIterator::operator++() { MOZ_ASSERT(!done()); if (fp_ != activation_->entryFrame_) { @@ -1604,25 +1599,23 @@ InterpreterFrameIterator::operator++() } return *this; } void Activation::registerProfiling() { MOZ_ASSERT(isProfiling()); - JSRuntime::AutoLockForInterrupt lock(cx_->asJSContext()->runtime()); cx_->perThreadData->profilingActivation_ = this; } void Activation::unregisterProfiling() { MOZ_ASSERT(isProfiling()); - JSRuntime::AutoLockForInterrupt lock(cx_->asJSContext()->runtime()); MOZ_ASSERT(cx_->perThreadData->profilingActivation_ == this); cx_->perThreadData->profilingActivation_ = prevProfiling_; } ActivationIterator::ActivationIterator(JSRuntime *rt) : jitTop_(rt->mainThread.jitTop), activation_(rt->mainThread.activation_) {