michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef PROFILER_PSEUDO_STACK_H_ michael@0: #define PROFILER_PSEUDO_STACK_H_ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/NullPtr.h" michael@0: #include michael@0: #include "js/ProfilingStack.h" michael@0: #include michael@0: #include "mozilla/Atomics.h" michael@0: michael@0: /* we duplicate this code here to avoid header dependencies michael@0: * which make it more difficult to include in other places */ michael@0: #if defined(_M_X64) || defined(__x86_64__) michael@0: #define V8_HOST_ARCH_X64 1 michael@0: #elif defined(_M_IX86) || defined(__i386__) || defined(__i386) michael@0: #define V8_HOST_ARCH_IA32 1 michael@0: #elif defined(__ARMEL__) michael@0: #define V8_HOST_ARCH_ARM 1 michael@0: #else michael@0: #warning Please add support for your architecture in chromium_types.h michael@0: #endif michael@0: michael@0: // STORE_SEQUENCER: Because signals can interrupt our profile modification michael@0: // we need to make stores are not re-ordered by the compiler michael@0: // or hardware to make sure the profile is consistent at michael@0: // every point the signal can fire. michael@0: #ifdef V8_HOST_ARCH_ARM michael@0: // TODO Is there something cheaper that will prevent michael@0: // memory stores from being reordered michael@0: michael@0: typedef void (*LinuxKernelMemoryBarrierFunc)(void); michael@0: LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = michael@0: (LinuxKernelMemoryBarrierFunc) 0xffff0fa0; michael@0: michael@0: # define STORE_SEQUENCER() pLinuxKernelMemoryBarrier() michael@0: #elif defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64) michael@0: # if defined(_MSC_VER) michael@0: #if _MSC_VER > 1400 michael@0: # include michael@0: #else // _MSC_VER > 1400 michael@0: // MSVC2005 has a name collision bug caused when both and are included together. michael@0: #ifdef _WINNT_ michael@0: # define _interlockedbittestandreset _interlockedbittestandreset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR michael@0: # define _interlockedbittestandset _interlockedbittestandset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR michael@0: # include michael@0: #else michael@0: # include michael@0: # define _interlockedbittestandreset _interlockedbittestandreset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR michael@0: # define _interlockedbittestandset _interlockedbittestandset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR michael@0: #endif michael@0: // Even though MSVC2005 has the intrinsic _ReadWriteBarrier, it fails to link to it when it's michael@0: // not explicitly declared. michael@0: # pragma intrinsic(_ReadWriteBarrier) michael@0: #endif // _MSC_VER > 1400 michael@0: # define STORE_SEQUENCER() _ReadWriteBarrier(); michael@0: # elif defined(__INTEL_COMPILER) michael@0: # define STORE_SEQUENCER() __memory_barrier(); michael@0: # elif __GNUC__ michael@0: # define STORE_SEQUENCER() asm volatile("" ::: "memory"); michael@0: # else michael@0: # error "Memory clobber not supported for your compiler." michael@0: # endif michael@0: #else michael@0: # error "Memory clobber not supported for your platform." michael@0: #endif michael@0: michael@0: // We can't include because it causes issues on OS X, so we use michael@0: // our own min function. michael@0: static inline uint32_t sMin(uint32_t l, uint32_t r) { michael@0: return l < r ? l : r; michael@0: } michael@0: michael@0: // A stack entry exists to allow the JS engine to inform SPS of the current michael@0: // backtrace, but also to instrument particular points in C++ in case stack michael@0: // walking is not available on the platform we are running on. michael@0: // michael@0: // Each entry has a descriptive string, a relevant stack address, and some extra michael@0: // information the JS engine might want to inform SPS of. This class inherits michael@0: // from the JS engine's version of the entry to ensure that the size and layout michael@0: // of the two representations are consistent. michael@0: class StackEntry : public js::ProfileEntry michael@0: { michael@0: public: michael@0: michael@0: bool isCopyLabel() const volatile { michael@0: return !((uintptr_t)stackAddress() & 0x1); michael@0: } michael@0: michael@0: void setStackAddressCopy(void *sparg, bool copy) volatile { michael@0: // Tagged pointer. Less significant bit used to track if mLabel needs a michael@0: // copy. Note that we don't need the last bit of the stack address for michael@0: // proper ordering. This is optimized for encoding within the JS engine's michael@0: // instrumentation, so we do the extra work here of encoding a bit. michael@0: // Last bit 1 = Don't copy, Last bit 0 = Copy. michael@0: if (copy) { michael@0: setStackAddress(reinterpret_cast( michael@0: reinterpret_cast(sparg) & ~NoCopyBit)); michael@0: } else { michael@0: setStackAddress(reinterpret_cast( michael@0: reinterpret_cast(sparg) | NoCopyBit)); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: class ProfilerMarkerPayload; michael@0: template michael@0: class ProfilerLinkedList; michael@0: class JSStreamWriter; michael@0: class JSCustomArray; michael@0: class ThreadProfile; michael@0: class ProfilerMarker { michael@0: friend class ProfilerLinkedList; michael@0: public: michael@0: ProfilerMarker(const char* aMarkerName, michael@0: ProfilerMarkerPayload* aPayload = nullptr, michael@0: float aTime = 0); michael@0: michael@0: ~ProfilerMarker(); michael@0: michael@0: const char* GetMarkerName() const { michael@0: return mMarkerName; michael@0: } michael@0: michael@0: void michael@0: StreamJSObject(JSStreamWriter& b) const; michael@0: michael@0: void SetGeneration(int aGenID); michael@0: michael@0: bool HasExpired(int aGenID) const { michael@0: return mGenID + 2 <= aGenID; michael@0: } michael@0: michael@0: float GetTime(); michael@0: michael@0: private: michael@0: char* mMarkerName; michael@0: ProfilerMarkerPayload* mPayload; michael@0: ProfilerMarker* mNext; michael@0: float mTime; michael@0: int mGenID; michael@0: }; michael@0: michael@0: // Foward declaration michael@0: typedef struct _UnwinderThreadBuffer UnwinderThreadBuffer; michael@0: michael@0: /** michael@0: * This struct is used to add a mNext field to UnwinderThreadBuffer objects for michael@0: * use with ProfilerLinkedList. It is done this way so that UnwinderThreadBuffer michael@0: * may continue to be opaque with respect to code outside of UnwinderThread2.cpp michael@0: */ michael@0: struct LinkedUWTBuffer michael@0: { michael@0: LinkedUWTBuffer() michael@0: :mNext(nullptr) michael@0: {} michael@0: virtual ~LinkedUWTBuffer() {} michael@0: virtual UnwinderThreadBuffer* GetBuffer() = 0; michael@0: LinkedUWTBuffer* mNext; michael@0: }; michael@0: michael@0: template michael@0: class ProfilerLinkedList { michael@0: public: michael@0: ProfilerLinkedList() michael@0: : mHead(nullptr) michael@0: , mTail(nullptr) michael@0: {} michael@0: michael@0: void insert(T* elem) michael@0: { michael@0: if (!mTail) { michael@0: mHead = elem; michael@0: mTail = elem; michael@0: } else { michael@0: mTail->mNext = elem; michael@0: mTail = elem; michael@0: } michael@0: elem->mNext = nullptr; michael@0: } michael@0: michael@0: T* popHead() michael@0: { michael@0: if (!mHead) { michael@0: MOZ_ASSERT(false); michael@0: return nullptr; michael@0: } michael@0: michael@0: T* head = mHead; michael@0: michael@0: mHead = head->mNext; michael@0: if (!mHead) { michael@0: mTail = nullptr; michael@0: } michael@0: michael@0: return head; michael@0: } michael@0: michael@0: const T* peek() { michael@0: return mHead; michael@0: } michael@0: michael@0: private: michael@0: T* mHead; michael@0: T* mTail; michael@0: }; michael@0: michael@0: typedef ProfilerLinkedList ProfilerMarkerLinkedList; michael@0: typedef ProfilerLinkedList UWTBufferLinkedList; michael@0: michael@0: class PendingMarkers { michael@0: public: michael@0: PendingMarkers() michael@0: : mSignalLock(false) michael@0: {} michael@0: michael@0: ~PendingMarkers(); michael@0: michael@0: void addMarker(ProfilerMarker *aMarker); michael@0: michael@0: void updateGeneration(int aGenID); michael@0: michael@0: /** michael@0: * Track a marker which has been inserted into the ThreadProfile. michael@0: * This marker can safely be deleted once the generation has michael@0: * expired. michael@0: */ michael@0: void addStoredMarker(ProfilerMarker *aStoredMarker); michael@0: michael@0: // called within signal. Function must be reentrant michael@0: ProfilerMarkerLinkedList* getPendingMarkers() michael@0: { michael@0: // if mSignalLock then the stack is inconsistent because it's being michael@0: // modified by the profiled thread. Post pone these markers michael@0: // for the next sample. The odds of a livelock are nearly impossible michael@0: // and would show up in a profile as many sample in 'addMarker' thus michael@0: // we ignore this scenario. michael@0: if (mSignalLock) { michael@0: return nullptr; michael@0: } michael@0: return &mPendingMarkers; michael@0: } michael@0: michael@0: void clearMarkers() michael@0: { michael@0: while (mPendingMarkers.peek()) { michael@0: delete mPendingMarkers.popHead(); michael@0: } michael@0: while (mStoredMarkers.peek()) { michael@0: delete mStoredMarkers.popHead(); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: // Keep a list of active markers to be applied to the next sample taken michael@0: ProfilerMarkerLinkedList mPendingMarkers; michael@0: ProfilerMarkerLinkedList mStoredMarkers; michael@0: // If this is set then it's not safe to read mStackPointer from the signal handler michael@0: volatile bool mSignalLock; michael@0: // We don't want to modify _markers from within the signal so we allow michael@0: // it to queue a clear operation. michael@0: volatile mozilla::sig_safe_t mGenID; michael@0: }; michael@0: michael@0: class PendingUWTBuffers michael@0: { michael@0: public: michael@0: PendingUWTBuffers() michael@0: : mSignalLock(false) michael@0: { michael@0: } michael@0: michael@0: void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) michael@0: { michael@0: MOZ_ASSERT(aBuff); michael@0: mSignalLock = true; michael@0: STORE_SEQUENCER(); michael@0: mPendingUWTBuffers.insert(aBuff); michael@0: STORE_SEQUENCER(); michael@0: mSignalLock = false; michael@0: } michael@0: michael@0: // called within signal. Function must be reentrant michael@0: UWTBufferLinkedList* getLinkedUWTBuffers() michael@0: { michael@0: if (mSignalLock) { michael@0: return nullptr; michael@0: } michael@0: return &mPendingUWTBuffers; michael@0: } michael@0: michael@0: private: michael@0: UWTBufferLinkedList mPendingUWTBuffers; michael@0: volatile bool mSignalLock; michael@0: }; michael@0: michael@0: // Stub eventMarker function for js-engine event generation. michael@0: void ProfilerJSEventMarker(const char *event); michael@0: michael@0: // the PseudoStack members are read by signal michael@0: // handlers, so the mutation of them needs to be signal-safe. michael@0: struct PseudoStack michael@0: { michael@0: public: michael@0: PseudoStack() michael@0: : mStackPointer(0) michael@0: , mSleepId(0) michael@0: , mSleepIdObserved(0) michael@0: , mSleeping(false) michael@0: , mRuntime(nullptr) michael@0: , mStartJSSampling(false) michael@0: , mPrivacyMode(false) michael@0: { } michael@0: michael@0: ~PseudoStack() { michael@0: if (mStackPointer != 0) { michael@0: // We're releasing the pseudostack while it's still in use. michael@0: // The label macros keep a non ref counted reference to the michael@0: // stack to avoid a TLS. If these are not all cleared we will michael@0: // get a use-after-free so better to crash now. michael@0: abort(); michael@0: } michael@0: } michael@0: michael@0: // This is called on every profiler restart. Put things that should happen at that time here. michael@0: void reinitializeOnResume() { michael@0: // This is needed to cause an initial sample to be taken from sleeping threads. Otherwise sleeping michael@0: // threads would not have any samples to copy forward while sleeping. michael@0: mSleepId++; michael@0: } michael@0: michael@0: void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) michael@0: { michael@0: mPendingUWTBuffers.addLinkedUWTBuffer(aBuff); michael@0: } michael@0: michael@0: UWTBufferLinkedList* getLinkedUWTBuffers() michael@0: { michael@0: return mPendingUWTBuffers.getLinkedUWTBuffers(); michael@0: } michael@0: michael@0: void addMarker(const char *aMarkerStr, ProfilerMarkerPayload *aPayload, float aTime) michael@0: { michael@0: ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime); michael@0: mPendingMarkers.addMarker(marker); michael@0: } michael@0: michael@0: void addStoredMarker(ProfilerMarker *aStoredMarker) { michael@0: mPendingMarkers.addStoredMarker(aStoredMarker); michael@0: } michael@0: michael@0: void updateGeneration(int aGenID) { michael@0: mPendingMarkers.updateGeneration(aGenID); michael@0: } michael@0: michael@0: // called within signal. Function must be reentrant michael@0: ProfilerMarkerLinkedList* getPendingMarkers() michael@0: { michael@0: return mPendingMarkers.getPendingMarkers(); michael@0: } michael@0: michael@0: void push(const char *aName, uint32_t line) michael@0: { michael@0: push(aName, nullptr, false, line); michael@0: } michael@0: michael@0: void push(const char *aName, void *aStackAddress, bool aCopy, uint32_t line) michael@0: { michael@0: if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) { michael@0: mStackPointer++; michael@0: return; michael@0: } michael@0: michael@0: // Make sure we increment the pointer after the name has michael@0: // been written such that mStack is always consistent. michael@0: mStack[mStackPointer].setLabel(aName); michael@0: mStack[mStackPointer].setStackAddressCopy(aStackAddress, aCopy); michael@0: mStack[mStackPointer].setLine(line); michael@0: michael@0: // Prevent the optimizer from re-ordering these instructions michael@0: STORE_SEQUENCER(); michael@0: mStackPointer++; michael@0: } michael@0: void pop() michael@0: { michael@0: mStackPointer--; michael@0: } michael@0: bool isEmpty() michael@0: { michael@0: return mStackPointer == 0; michael@0: } michael@0: uint32_t stackSize() const michael@0: { michael@0: return sMin(mStackPointer, mozilla::sig_safe_t(mozilla::ArrayLength(mStack))); michael@0: } michael@0: michael@0: void sampleRuntime(JSRuntime *runtime) { michael@0: mRuntime = runtime; michael@0: if (!runtime) { michael@0: // JS shut down michael@0: return; michael@0: } michael@0: michael@0: static_assert(sizeof(mStack[0]) == sizeof(js::ProfileEntry), michael@0: "mStack must be binary compatible with js::ProfileEntry."); michael@0: js::SetRuntimeProfilingStack(runtime, michael@0: (js::ProfileEntry*) mStack, michael@0: (uint32_t*) &mStackPointer, michael@0: uint32_t(mozilla::ArrayLength(mStack))); michael@0: if (mStartJSSampling) michael@0: enableJSSampling(); michael@0: } michael@0: void enableJSSampling() { michael@0: if (mRuntime) { michael@0: js::EnableRuntimeProfilingStack(mRuntime, true); michael@0: js::RegisterRuntimeProfilingEventMarker(mRuntime, &ProfilerJSEventMarker); michael@0: mStartJSSampling = false; michael@0: } else { michael@0: mStartJSSampling = true; michael@0: } michael@0: } michael@0: void jsOperationCallback() { michael@0: if (mStartJSSampling) michael@0: enableJSSampling(); michael@0: } michael@0: void disableJSSampling() { michael@0: mStartJSSampling = false; michael@0: if (mRuntime) michael@0: js::EnableRuntimeProfilingStack(mRuntime, false); michael@0: } michael@0: michael@0: // Keep a list of active checkpoints michael@0: StackEntry volatile mStack[1024]; michael@0: private: michael@0: // Keep a list of pending markers that must be moved michael@0: // to the circular buffer michael@0: PendingMarkers mPendingMarkers; michael@0: // List of LinkedUWTBuffers that must be processed on the next tick michael@0: PendingUWTBuffers mPendingUWTBuffers; michael@0: // This may exceed the length of mStack, so instead use the stackSize() method michael@0: // to determine the number of valid samples in mStack michael@0: mozilla::sig_safe_t mStackPointer; michael@0: // Incremented at every sleep/wake up of the thread michael@0: int mSleepId; michael@0: // Previous id observed. If this is not the same as mSleepId, this thread is not sleeping in the same place any more michael@0: mozilla::Atomic mSleepIdObserved; michael@0: // Keeps tack of whether the thread is sleeping or not (1 when sleeping 0 when awake) michael@0: mozilla::Atomic mSleeping; michael@0: public: michael@0: // The runtime which is being sampled michael@0: JSRuntime *mRuntime; michael@0: // Start JS Profiling when possible michael@0: bool mStartJSSampling; michael@0: bool mPrivacyMode; michael@0: michael@0: enum SleepState {NOT_SLEEPING, SLEEPING_FIRST, SLEEPING_AGAIN}; michael@0: michael@0: // The first time this is called per sleep cycle we return SLEEPING_FIRST michael@0: // and any other subsequent call within the same sleep cycle we return SLEEPING_AGAIN michael@0: SleepState observeSleeping() { michael@0: if (mSleeping != 0) { michael@0: if (mSleepIdObserved == mSleepId) { michael@0: return SLEEPING_AGAIN; michael@0: } else { michael@0: mSleepIdObserved = mSleepId; michael@0: return SLEEPING_FIRST; michael@0: } michael@0: } else { michael@0: return NOT_SLEEPING; michael@0: } michael@0: } michael@0: michael@0: michael@0: // Call this whenever the current thread sleeps or wakes up michael@0: // Calling setSleeping with the same value twice in a row is an error michael@0: void setSleeping(int sleeping) { michael@0: MOZ_ASSERT(mSleeping != sleeping); michael@0: mSleepId++; michael@0: mSleeping = sleeping; michael@0: } michael@0: }; michael@0: michael@0: #endif michael@0: