|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #ifndef PROFILER_PSEUDO_STACK_H_ |
|
7 #define PROFILER_PSEUDO_STACK_H_ |
|
8 |
|
9 #include "mozilla/ArrayUtils.h" |
|
10 #include "mozilla/NullPtr.h" |
|
11 #include <stdint.h> |
|
12 #include "js/ProfilingStack.h" |
|
13 #include <stdlib.h> |
|
14 #include "mozilla/Atomics.h" |
|
15 |
|
16 /* we duplicate this code here to avoid header dependencies |
|
17 * which make it more difficult to include in other places */ |
|
18 #if defined(_M_X64) || defined(__x86_64__) |
|
19 #define V8_HOST_ARCH_X64 1 |
|
20 #elif defined(_M_IX86) || defined(__i386__) || defined(__i386) |
|
21 #define V8_HOST_ARCH_IA32 1 |
|
22 #elif defined(__ARMEL__) |
|
23 #define V8_HOST_ARCH_ARM 1 |
|
24 #else |
|
25 #warning Please add support for your architecture in chromium_types.h |
|
26 #endif |
|
27 |
|
28 // STORE_SEQUENCER: Because signals can interrupt our profile modification |
|
29 // we need to make stores are not re-ordered by the compiler |
|
30 // or hardware to make sure the profile is consistent at |
|
31 // every point the signal can fire. |
|
32 #ifdef V8_HOST_ARCH_ARM |
|
33 // TODO Is there something cheaper that will prevent |
|
34 // memory stores from being reordered |
|
35 |
|
36 typedef void (*LinuxKernelMemoryBarrierFunc)(void); |
|
37 LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = |
|
38 (LinuxKernelMemoryBarrierFunc) 0xffff0fa0; |
|
39 |
|
40 # define STORE_SEQUENCER() pLinuxKernelMemoryBarrier() |
|
41 #elif defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64) |
|
42 # if defined(_MSC_VER) |
|
43 #if _MSC_VER > 1400 |
|
44 # include <intrin.h> |
|
45 #else // _MSC_VER > 1400 |
|
46 // MSVC2005 has a name collision bug caused when both <intrin.h> and <winnt.h> are included together. |
|
47 #ifdef _WINNT_ |
|
48 # define _interlockedbittestandreset _interlockedbittestandreset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR |
|
49 # define _interlockedbittestandset _interlockedbittestandset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR |
|
50 # include <intrin.h> |
|
51 #else |
|
52 # include <intrin.h> |
|
53 # define _interlockedbittestandreset _interlockedbittestandreset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR |
|
54 # define _interlockedbittestandset _interlockedbittestandset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR |
|
55 #endif |
|
56 // Even though MSVC2005 has the intrinsic _ReadWriteBarrier, it fails to link to it when it's |
|
57 // not explicitly declared. |
|
58 # pragma intrinsic(_ReadWriteBarrier) |
|
59 #endif // _MSC_VER > 1400 |
|
60 # define STORE_SEQUENCER() _ReadWriteBarrier(); |
|
61 # elif defined(__INTEL_COMPILER) |
|
62 # define STORE_SEQUENCER() __memory_barrier(); |
|
63 # elif __GNUC__ |
|
64 # define STORE_SEQUENCER() asm volatile("" ::: "memory"); |
|
65 # else |
|
66 # error "Memory clobber not supported for your compiler." |
|
67 # endif |
|
68 #else |
|
69 # error "Memory clobber not supported for your platform." |
|
70 #endif |
|
71 |
|
72 // We can't include <algorithm> because it causes issues on OS X, so we use |
|
73 // our own min function. |
|
74 static inline uint32_t sMin(uint32_t l, uint32_t r) { |
|
75 return l < r ? l : r; |
|
76 } |
|
77 |
|
78 // A stack entry exists to allow the JS engine to inform SPS of the current |
|
79 // backtrace, but also to instrument particular points in C++ in case stack |
|
80 // walking is not available on the platform we are running on. |
|
81 // |
|
82 // Each entry has a descriptive string, a relevant stack address, and some extra |
|
83 // information the JS engine might want to inform SPS of. This class inherits |
|
84 // from the JS engine's version of the entry to ensure that the size and layout |
|
85 // of the two representations are consistent. |
|
86 class StackEntry : public js::ProfileEntry |
|
87 { |
|
88 public: |
|
89 |
|
90 bool isCopyLabel() const volatile { |
|
91 return !((uintptr_t)stackAddress() & 0x1); |
|
92 } |
|
93 |
|
94 void setStackAddressCopy(void *sparg, bool copy) volatile { |
|
95 // Tagged pointer. Less significant bit used to track if mLabel needs a |
|
96 // copy. Note that we don't need the last bit of the stack address for |
|
97 // proper ordering. This is optimized for encoding within the JS engine's |
|
98 // instrumentation, so we do the extra work here of encoding a bit. |
|
99 // Last bit 1 = Don't copy, Last bit 0 = Copy. |
|
100 if (copy) { |
|
101 setStackAddress(reinterpret_cast<void*>( |
|
102 reinterpret_cast<uintptr_t>(sparg) & ~NoCopyBit)); |
|
103 } else { |
|
104 setStackAddress(reinterpret_cast<void*>( |
|
105 reinterpret_cast<uintptr_t>(sparg) | NoCopyBit)); |
|
106 } |
|
107 } |
|
108 }; |
|
109 |
|
110 class ProfilerMarkerPayload; |
|
111 template<typename T> |
|
112 class ProfilerLinkedList; |
|
113 class JSStreamWriter; |
|
114 class JSCustomArray; |
|
115 class ThreadProfile; |
|
116 class ProfilerMarker { |
|
117 friend class ProfilerLinkedList<ProfilerMarker>; |
|
118 public: |
|
119 ProfilerMarker(const char* aMarkerName, |
|
120 ProfilerMarkerPayload* aPayload = nullptr, |
|
121 float aTime = 0); |
|
122 |
|
123 ~ProfilerMarker(); |
|
124 |
|
125 const char* GetMarkerName() const { |
|
126 return mMarkerName; |
|
127 } |
|
128 |
|
129 void |
|
130 StreamJSObject(JSStreamWriter& b) const; |
|
131 |
|
132 void SetGeneration(int aGenID); |
|
133 |
|
134 bool HasExpired(int aGenID) const { |
|
135 return mGenID + 2 <= aGenID; |
|
136 } |
|
137 |
|
138 float GetTime(); |
|
139 |
|
140 private: |
|
141 char* mMarkerName; |
|
142 ProfilerMarkerPayload* mPayload; |
|
143 ProfilerMarker* mNext; |
|
144 float mTime; |
|
145 int mGenID; |
|
146 }; |
|
147 |
|
148 // Foward declaration |
|
149 typedef struct _UnwinderThreadBuffer UnwinderThreadBuffer; |
|
150 |
|
151 /** |
|
152 * This struct is used to add a mNext field to UnwinderThreadBuffer objects for |
|
153 * use with ProfilerLinkedList. It is done this way so that UnwinderThreadBuffer |
|
154 * may continue to be opaque with respect to code outside of UnwinderThread2.cpp |
|
155 */ |
|
156 struct LinkedUWTBuffer |
|
157 { |
|
158 LinkedUWTBuffer() |
|
159 :mNext(nullptr) |
|
160 {} |
|
161 virtual ~LinkedUWTBuffer() {} |
|
162 virtual UnwinderThreadBuffer* GetBuffer() = 0; |
|
163 LinkedUWTBuffer* mNext; |
|
164 }; |
|
165 |
|
166 template<typename T> |
|
167 class ProfilerLinkedList { |
|
168 public: |
|
169 ProfilerLinkedList() |
|
170 : mHead(nullptr) |
|
171 , mTail(nullptr) |
|
172 {} |
|
173 |
|
174 void insert(T* elem) |
|
175 { |
|
176 if (!mTail) { |
|
177 mHead = elem; |
|
178 mTail = elem; |
|
179 } else { |
|
180 mTail->mNext = elem; |
|
181 mTail = elem; |
|
182 } |
|
183 elem->mNext = nullptr; |
|
184 } |
|
185 |
|
186 T* popHead() |
|
187 { |
|
188 if (!mHead) { |
|
189 MOZ_ASSERT(false); |
|
190 return nullptr; |
|
191 } |
|
192 |
|
193 T* head = mHead; |
|
194 |
|
195 mHead = head->mNext; |
|
196 if (!mHead) { |
|
197 mTail = nullptr; |
|
198 } |
|
199 |
|
200 return head; |
|
201 } |
|
202 |
|
203 const T* peek() { |
|
204 return mHead; |
|
205 } |
|
206 |
|
207 private: |
|
208 T* mHead; |
|
209 T* mTail; |
|
210 }; |
|
211 |
|
212 typedef ProfilerLinkedList<ProfilerMarker> ProfilerMarkerLinkedList; |
|
213 typedef ProfilerLinkedList<LinkedUWTBuffer> UWTBufferLinkedList; |
|
214 |
|
215 class PendingMarkers { |
|
216 public: |
|
217 PendingMarkers() |
|
218 : mSignalLock(false) |
|
219 {} |
|
220 |
|
221 ~PendingMarkers(); |
|
222 |
|
223 void addMarker(ProfilerMarker *aMarker); |
|
224 |
|
225 void updateGeneration(int aGenID); |
|
226 |
|
227 /** |
|
228 * Track a marker which has been inserted into the ThreadProfile. |
|
229 * This marker can safely be deleted once the generation has |
|
230 * expired. |
|
231 */ |
|
232 void addStoredMarker(ProfilerMarker *aStoredMarker); |
|
233 |
|
234 // called within signal. Function must be reentrant |
|
235 ProfilerMarkerLinkedList* getPendingMarkers() |
|
236 { |
|
237 // if mSignalLock then the stack is inconsistent because it's being |
|
238 // modified by the profiled thread. Post pone these markers |
|
239 // for the next sample. The odds of a livelock are nearly impossible |
|
240 // and would show up in a profile as many sample in 'addMarker' thus |
|
241 // we ignore this scenario. |
|
242 if (mSignalLock) { |
|
243 return nullptr; |
|
244 } |
|
245 return &mPendingMarkers; |
|
246 } |
|
247 |
|
248 void clearMarkers() |
|
249 { |
|
250 while (mPendingMarkers.peek()) { |
|
251 delete mPendingMarkers.popHead(); |
|
252 } |
|
253 while (mStoredMarkers.peek()) { |
|
254 delete mStoredMarkers.popHead(); |
|
255 } |
|
256 } |
|
257 |
|
258 private: |
|
259 // Keep a list of active markers to be applied to the next sample taken |
|
260 ProfilerMarkerLinkedList mPendingMarkers; |
|
261 ProfilerMarkerLinkedList mStoredMarkers; |
|
262 // If this is set then it's not safe to read mStackPointer from the signal handler |
|
263 volatile bool mSignalLock; |
|
264 // We don't want to modify _markers from within the signal so we allow |
|
265 // it to queue a clear operation. |
|
266 volatile mozilla::sig_safe_t mGenID; |
|
267 }; |
|
268 |
|
269 class PendingUWTBuffers |
|
270 { |
|
271 public: |
|
272 PendingUWTBuffers() |
|
273 : mSignalLock(false) |
|
274 { |
|
275 } |
|
276 |
|
277 void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) |
|
278 { |
|
279 MOZ_ASSERT(aBuff); |
|
280 mSignalLock = true; |
|
281 STORE_SEQUENCER(); |
|
282 mPendingUWTBuffers.insert(aBuff); |
|
283 STORE_SEQUENCER(); |
|
284 mSignalLock = false; |
|
285 } |
|
286 |
|
287 // called within signal. Function must be reentrant |
|
288 UWTBufferLinkedList* getLinkedUWTBuffers() |
|
289 { |
|
290 if (mSignalLock) { |
|
291 return nullptr; |
|
292 } |
|
293 return &mPendingUWTBuffers; |
|
294 } |
|
295 |
|
296 private: |
|
297 UWTBufferLinkedList mPendingUWTBuffers; |
|
298 volatile bool mSignalLock; |
|
299 }; |
|
300 |
|
301 // Stub eventMarker function for js-engine event generation. |
|
302 void ProfilerJSEventMarker(const char *event); |
|
303 |
|
304 // the PseudoStack members are read by signal |
|
305 // handlers, so the mutation of them needs to be signal-safe. |
|
306 struct PseudoStack |
|
307 { |
|
308 public: |
|
309 PseudoStack() |
|
310 : mStackPointer(0) |
|
311 , mSleepId(0) |
|
312 , mSleepIdObserved(0) |
|
313 , mSleeping(false) |
|
314 , mRuntime(nullptr) |
|
315 , mStartJSSampling(false) |
|
316 , mPrivacyMode(false) |
|
317 { } |
|
318 |
|
319 ~PseudoStack() { |
|
320 if (mStackPointer != 0) { |
|
321 // We're releasing the pseudostack while it's still in use. |
|
322 // The label macros keep a non ref counted reference to the |
|
323 // stack to avoid a TLS. If these are not all cleared we will |
|
324 // get a use-after-free so better to crash now. |
|
325 abort(); |
|
326 } |
|
327 } |
|
328 |
|
329 // This is called on every profiler restart. Put things that should happen at that time here. |
|
330 void reinitializeOnResume() { |
|
331 // This is needed to cause an initial sample to be taken from sleeping threads. Otherwise sleeping |
|
332 // threads would not have any samples to copy forward while sleeping. |
|
333 mSleepId++; |
|
334 } |
|
335 |
|
336 void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) |
|
337 { |
|
338 mPendingUWTBuffers.addLinkedUWTBuffer(aBuff); |
|
339 } |
|
340 |
|
341 UWTBufferLinkedList* getLinkedUWTBuffers() |
|
342 { |
|
343 return mPendingUWTBuffers.getLinkedUWTBuffers(); |
|
344 } |
|
345 |
|
346 void addMarker(const char *aMarkerStr, ProfilerMarkerPayload *aPayload, float aTime) |
|
347 { |
|
348 ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime); |
|
349 mPendingMarkers.addMarker(marker); |
|
350 } |
|
351 |
|
352 void addStoredMarker(ProfilerMarker *aStoredMarker) { |
|
353 mPendingMarkers.addStoredMarker(aStoredMarker); |
|
354 } |
|
355 |
|
356 void updateGeneration(int aGenID) { |
|
357 mPendingMarkers.updateGeneration(aGenID); |
|
358 } |
|
359 |
|
360 // called within signal. Function must be reentrant |
|
361 ProfilerMarkerLinkedList* getPendingMarkers() |
|
362 { |
|
363 return mPendingMarkers.getPendingMarkers(); |
|
364 } |
|
365 |
|
366 void push(const char *aName, uint32_t line) |
|
367 { |
|
368 push(aName, nullptr, false, line); |
|
369 } |
|
370 |
|
371 void push(const char *aName, void *aStackAddress, bool aCopy, uint32_t line) |
|
372 { |
|
373 if (size_t(mStackPointer) >= mozilla::ArrayLength(mStack)) { |
|
374 mStackPointer++; |
|
375 return; |
|
376 } |
|
377 |
|
378 // Make sure we increment the pointer after the name has |
|
379 // been written such that mStack is always consistent. |
|
380 mStack[mStackPointer].setLabel(aName); |
|
381 mStack[mStackPointer].setStackAddressCopy(aStackAddress, aCopy); |
|
382 mStack[mStackPointer].setLine(line); |
|
383 |
|
384 // Prevent the optimizer from re-ordering these instructions |
|
385 STORE_SEQUENCER(); |
|
386 mStackPointer++; |
|
387 } |
|
388 void pop() |
|
389 { |
|
390 mStackPointer--; |
|
391 } |
|
392 bool isEmpty() |
|
393 { |
|
394 return mStackPointer == 0; |
|
395 } |
|
396 uint32_t stackSize() const |
|
397 { |
|
398 return sMin(mStackPointer, mozilla::sig_safe_t(mozilla::ArrayLength(mStack))); |
|
399 } |
|
400 |
|
401 void sampleRuntime(JSRuntime *runtime) { |
|
402 mRuntime = runtime; |
|
403 if (!runtime) { |
|
404 // JS shut down |
|
405 return; |
|
406 } |
|
407 |
|
408 static_assert(sizeof(mStack[0]) == sizeof(js::ProfileEntry), |
|
409 "mStack must be binary compatible with js::ProfileEntry."); |
|
410 js::SetRuntimeProfilingStack(runtime, |
|
411 (js::ProfileEntry*) mStack, |
|
412 (uint32_t*) &mStackPointer, |
|
413 uint32_t(mozilla::ArrayLength(mStack))); |
|
414 if (mStartJSSampling) |
|
415 enableJSSampling(); |
|
416 } |
|
417 void enableJSSampling() { |
|
418 if (mRuntime) { |
|
419 js::EnableRuntimeProfilingStack(mRuntime, true); |
|
420 js::RegisterRuntimeProfilingEventMarker(mRuntime, &ProfilerJSEventMarker); |
|
421 mStartJSSampling = false; |
|
422 } else { |
|
423 mStartJSSampling = true; |
|
424 } |
|
425 } |
|
426 void jsOperationCallback() { |
|
427 if (mStartJSSampling) |
|
428 enableJSSampling(); |
|
429 } |
|
430 void disableJSSampling() { |
|
431 mStartJSSampling = false; |
|
432 if (mRuntime) |
|
433 js::EnableRuntimeProfilingStack(mRuntime, false); |
|
434 } |
|
435 |
|
436 // Keep a list of active checkpoints |
|
437 StackEntry volatile mStack[1024]; |
|
438 private: |
|
439 // Keep a list of pending markers that must be moved |
|
440 // to the circular buffer |
|
441 PendingMarkers mPendingMarkers; |
|
442 // List of LinkedUWTBuffers that must be processed on the next tick |
|
443 PendingUWTBuffers mPendingUWTBuffers; |
|
444 // This may exceed the length of mStack, so instead use the stackSize() method |
|
445 // to determine the number of valid samples in mStack |
|
446 mozilla::sig_safe_t mStackPointer; |
|
447 // Incremented at every sleep/wake up of the thread |
|
448 int mSleepId; |
|
449 // Previous id observed. If this is not the same as mSleepId, this thread is not sleeping in the same place any more |
|
450 mozilla::Atomic<int> mSleepIdObserved; |
|
451 // Keeps tack of whether the thread is sleeping or not (1 when sleeping 0 when awake) |
|
452 mozilla::Atomic<int> mSleeping; |
|
453 public: |
|
454 // The runtime which is being sampled |
|
455 JSRuntime *mRuntime; |
|
456 // Start JS Profiling when possible |
|
457 bool mStartJSSampling; |
|
458 bool mPrivacyMode; |
|
459 |
|
460 enum SleepState {NOT_SLEEPING, SLEEPING_FIRST, SLEEPING_AGAIN}; |
|
461 |
|
462 // The first time this is called per sleep cycle we return SLEEPING_FIRST |
|
463 // and any other subsequent call within the same sleep cycle we return SLEEPING_AGAIN |
|
464 SleepState observeSleeping() { |
|
465 if (mSleeping != 0) { |
|
466 if (mSleepIdObserved == mSleepId) { |
|
467 return SLEEPING_AGAIN; |
|
468 } else { |
|
469 mSleepIdObserved = mSleepId; |
|
470 return SLEEPING_FIRST; |
|
471 } |
|
472 } else { |
|
473 return NOT_SLEEPING; |
|
474 } |
|
475 } |
|
476 |
|
477 |
|
478 // Call this whenever the current thread sleeps or wakes up |
|
479 // Calling setSleeping with the same value twice in a row is an error |
|
480 void setSleeping(int sleeping) { |
|
481 MOZ_ASSERT(mSleeping != sleeping); |
|
482 mSleepId++; |
|
483 mSleeping = sleeping; |
|
484 } |
|
485 }; |
|
486 |
|
487 #endif |
|
488 |