Coverage Report

Created: 2018-09-25 14:53

/work/obj-fuzz/dist/include/js/ProfilingStack.h
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2
 * vim: set ts=8 sts=4 et sw=4 tw=99:
3
 * This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#ifndef js_ProfilingStack_h
8
#define js_ProfilingStack_h
9
10
#include <algorithm>
11
#include <stdint.h>
12
13
#include "jstypes.h"
14
15
#include "js/TypeDecls.h"
16
#include "js/Utility.h"
17
18
#ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING
19
#pragma GCC diagnostic push
20
#pragma GCC diagnostic ignored "-Wattributes"
21
#endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING
22
23
class JS_PUBLIC_API(JSTracer);
24
25
#ifdef JS_BROKEN_GCC_ATTRIBUTE_WARNING
26
#pragma GCC diagnostic pop
27
#endif // JS_BROKEN_GCC_ATTRIBUTE_WARNING
28
29
class ProfilingStack;
30
31
// This file defines the classes ProfilingStack and ProfilingStackFrame.
32
// The ProfilingStack manages an array of ProfilingStackFrames.
33
// It keeps track of the "label stack" and the JS interpreter stack.
34
// The two stack types are interleaved.
35
//
36
// Usage:
37
//
38
//  ProfilingStack* profilingStack = ...;
39
//
40
//  // For label frames:
41
//  profilingStack->pushLabelFrame(...);
42
//  // Execute some code. When finished, pop the frame:
43
//  profilingStack->pop();
44
//
45
//  // For JS stack frames:
46
//  profilingStack->pushJSFrame(...);
47
//  // Execute some code. When finished, pop the frame:
48
//  profilingStack->pop();
49
//
50
//
51
// Concurrency considerations
52
//
53
// A thread's profiling stack (and the frames inside it) is only modified by
54
// that thread. However, the profiling stack can be *read* by a different thread,
55
// the sampler thread: Whenever the profiler wants to sample a given thread A,
56
// the following happens:
57
//  (1) Thread A is suspended.
58
//  (2) The sampler thread (thread S) reads the ProfilingStack of thread A,
59
//      including all ProfilingStackFrames that are currently in that stack
60
//      (profilingStack->frames[0..profilingStack->stackSize()]).
61
//  (3) Thread A is resumed.
62
//
63
// Thread suspension is achieved using platform-specific APIs; refer to each
64
// platform's Sampler::SuspendAndSampleAndResumeThread implementation in
65
// platform-*.cpp for details.
66
//
67
// When the thread is suspended, the values in profilingStack->stackPointer and in
68
// the stack frame range profilingStack->frames[0..profilingStack->stackPointer] need
69
// to be in a consistent state, so that thread S does not read partially-
70
// constructed stack frames. More specifically, we have two requirements:
71
//  (1) When adding a new frame at the top of the stack, its ProfilingStackFrame
72
//      data needs to be put in place *before* the stackPointer is incremented,
73
//      and the compiler + CPU need to know that this order matters.
74
//  (2) When popping an frame from the stack and then preparing the
75
//      ProfilingStackFrame data for the next frame that is about to be pushed,
76
//      the decrement of the stackPointer in pop() needs to happen *before* the
77
//      ProfilingStackFrame for the new frame is being popuplated, and the
78
//      compiler + CPU need to know that this order matters.
79
//
80
// We can express the relevance of these orderings in multiple ways.
81
// Option A is to make stackPointer an atomic with SequentiallyConsistent
82
// memory ordering. This would ensure that no writes in thread A would be
83
// reordered across any writes to stackPointer, which satisfies requirements
84
// (1) and (2) at the same time. Option A is the simplest.
85
// Option B is to use ReleaseAcquire memory ordering both for writes to
86
// stackPointer *and* for writes to ProfilingStackFrame fields. Release-stores
87
// ensure that all writes that happened *before this write in program order* are
88
// not reordered to happen after this write. ReleaseAcquire ordering places no
89
// requirements on the ordering of writes that happen *after* this write in
90
// program order.
91
// Using release-stores for writes to stackPointer expresses requirement (1),
92
// and using release-stores for writes to the ProfilingStackFrame fields
93
// expresses requirement (2).
94
//
95
// Option B is more complicated than option A, but has much better performance
96
// on x86/64: In a microbenchmark run on a Macbook Pro from 2017, switching
97
// from option A to option B reduced the overhead of pushing+popping a
98
// ProfilingStackFrame by 10 nanoseconds.
99
// On x86/64, release-stores require no explicit hardware barriers or lock
100
// instructions.
101
// On ARM/64, option B may be slower than option A, because the compiler will
102
// generate hardware barriers for every single release-store instead of just
103
// for the writes to stackPointer. However, the actual performance impact of
104
// this has not yet been measured on ARM, so we're currently using option B
105
// everywhere. This is something that we may want to change in the future once
106
// we've done measurements.
107
108
namespace js {
109
110
// A call stack can be specified to the JS engine such that all JS entry/exits
111
// to functions push/pop a stack frame to/from the specified stack.
112
//
113
// For more detailed information, see vm/GeckoProfiler.h.
114
//
115
class ProfilingStackFrame
116
{
117
    // A ProfilingStackFrame represents either a label frame or a JS frame.
118
119
    // WARNING WARNING WARNING
120
    //
121
    // All the fields below are Atomic<...,ReleaseAcquire>. This is needed so
122
    // that writes to these fields are release-writes, which ensures that
123
    // earlier writes in this thread don't get reordered after the writes to
124
    // these fields. In particular, the decrement of the stack pointer in
125
    // ProfilingStack::pop() is a write that *must* happen before the values in
126
    // this ProfilingStackFrame are changed. Otherwise, the sampler thread might
127
    // see an inconsistent state where the stack pointer still points to a
128
    // ProfilingStackFrame which has already been popped off the stack and whose
129
    // fields have now been partially repopulated with new values.
130
    // See the "Concurrency considerations" paragraph at the top of this file
131
    // for more details.
132
133
    // Descriptive label for this stack frame. Must be a static string! Can be
134
    // an empty string, but not a null pointer.
135
    mozilla::Atomic<const char*, mozilla::ReleaseAcquire,
136
                    mozilla::recordreplay::Behavior::DontPreserve> label_;
137
138
    // An additional descriptive string of this frame which is combined with
139
    // |label_| in profiler output. Need not be (and usually isn't) static. Can
140
    // be null.
141
    mozilla::Atomic<const char*, mozilla::ReleaseAcquire,
142
                    mozilla::recordreplay::Behavior::DontPreserve> dynamicString_;
143
144
    // Stack pointer for non-JS stack frames, the script pointer otherwise.
145
    mozilla::Atomic<void*, mozilla::ReleaseAcquire,
146
                    mozilla::recordreplay::Behavior::DontPreserve> spOrScript;
147
148
    // Line number for non-JS stack frames, the bytecode offset otherwise.
149
    mozilla::Atomic<int32_t, mozilla::ReleaseAcquire,
150
                    mozilla::recordreplay::Behavior::DontPreserve> lineOrPcOffset;
151
152
    // Bits 0...1 hold the Kind. Bits 2...31 hold the category.
153
    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
154
                    mozilla::recordreplay::Behavior::DontPreserve> kindAndCategory_;
155
156
    static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc);
157
158
  public:
159
5.12k
    ProfilingStackFrame() = default;
160
    ProfilingStackFrame& operator=(const ProfilingStackFrame& other)
161
0
    {
162
0
        label_ = other.label();
163
0
        dynamicString_ = other.dynamicString();
164
0
        void* spScript = other.spOrScript;
165
0
        spOrScript = spScript;
166
0
        int32_t offset = other.lineOrPcOffset;
167
0
        lineOrPcOffset = offset;
168
0
        uint32_t kindAndCategory = other.kindAndCategory_;
169
0
        kindAndCategory_ = kindAndCategory;
170
0
        return *this;
171
0
    }
172
173
    enum class Kind : uint32_t {
174
        // A regular label frame. These usually come from AutoProfilerLabel.
175
        LABEL = 0,
176
177
        // A special frame indicating the start of a run of JS profiling stack
178
        // frames. SP_MARKER frames are ignored, except for the sp field.
179
        // These frames are needed to get correct ordering between JS and LABEL
180
        // frames because JS frames don't carry sp information.
181
        // SP is short for "stack pointer".
182
        SP_MARKER = 1,
183
184
        // A normal JS frame.
185
        JS_NORMAL = 2,
186
187
        // An interpreter JS frame that has OSR-ed into baseline. JS_NORMAL
188
        // frames can be converted to JS_OSR and back. JS_OSR frames are
189
        // ignored.
190
        JS_OSR = 3,
191
192
        KIND_BITCOUNT = 2,
193
        KIND_MASK = (1 << KIND_BITCOUNT) - 1
194
    };
195
196
    // Keep these in sync with devtools/client/performance/modules/categories.js
197
    enum class Category : uint32_t {
198
        IDLE,
199
        OTHER,
200
        LAYOUT,
201
        JS,
202
        GCCC,
203
        NETWORK,
204
        GRAPHICS,
205
        DOM,
206
207
        FIRST    = OTHER,
208
        LAST     = DOM,
209
    };
210
211
    static_assert(uint32_t(Category::LAST) <= (UINT32_MAX >> uint32_t(Kind::KIND_BITCOUNT)),
212
                  "Too many categories to fit into u32 with two bits reserved for the kind");
213
214
    bool isLabelFrame() const
215
0
    {
216
0
        return kind() == Kind::LABEL;
217
0
    }
218
219
    bool isSpMarkerFrame() const
220
0
    {
221
0
        return kind() == Kind::SP_MARKER;
222
0
    }
223
224
    bool isJsFrame() const
225
    {
226
        Kind k = kind();
227
        return k == Kind::JS_NORMAL || k == Kind::JS_OSR;
228
    }
229
230
0
    void setLabel(const char* aLabel) { label_ = aLabel; }
231
0
    const char* label() const { return label_; }
232
233
0
    const char* dynamicString() const { return dynamicString_; }
234
235
    void initLabelFrame(const char* aLabel, const char* aDynamicString, void* sp,
236
                        uint32_t aLine, Category aCategory)
237
1.62M
    {
238
1.62M
        label_ = aLabel;
239
1.62M
        dynamicString_ = aDynamicString;
240
1.62M
        spOrScript = sp;
241
1.62M
        lineOrPcOffset = static_cast<int32_t>(aLine);
242
1.62M
        kindAndCategory_ = uint32_t(Kind::LABEL) | (uint32_t(aCategory) << uint32_t(Kind::KIND_BITCOUNT));
243
1.62M
        MOZ_ASSERT(isLabelFrame());
244
1.62M
    }
245
246
    void initSpMarkerFrame(void* sp)
247
1.62M
    {
248
1.62M
        label_ = "";
249
1.62M
        dynamicString_ = nullptr;
250
1.62M
        spOrScript = sp;
251
1.62M
        lineOrPcOffset = 0;
252
1.62M
        kindAndCategory_ = uint32_t(Kind::SP_MARKER) | (uint32_t(Category::OTHER) << uint32_t(Kind::KIND_BITCOUNT));
253
1.62M
        MOZ_ASSERT(isSpMarkerFrame());
254
1.62M
    }
255
256
    void initJsFrame(const char* aLabel, const char* aDynamicString, JSScript* aScript,
257
                     jsbytecode* aPc)
258
1.62M
    {
259
1.62M
        label_ = aLabel;
260
1.62M
        dynamicString_ = aDynamicString;
261
1.62M
        spOrScript = aScript;
262
1.62M
        lineOrPcOffset = pcToOffset(aScript, aPc);
263
1.62M
        kindAndCategory_ = uint32_t(Kind::JS_NORMAL) | (uint32_t(Category::JS) << uint32_t(Kind::KIND_BITCOUNT));
264
1.62M
        MOZ_ASSERT(isJsFrame());
265
1.62M
    }
266
267
0
    void setKind(Kind aKind) {
268
0
        kindAndCategory_ = uint32_t(aKind) | (uint32_t(category()) << uint32_t(Kind::KIND_BITCOUNT));
269
0
    }
270
271
    Kind kind() const {
272
        return Kind(kindAndCategory_ & uint32_t(Kind::KIND_MASK));
273
    }
274
275
0
    Category category() const {
276
0
        return Category(kindAndCategory_ >> uint32_t(Kind::KIND_BITCOUNT));
277
0
    }
278
279
0
    void* stackAddress() const {
280
0
        MOZ_ASSERT(!isJsFrame());
281
0
        return spOrScript;
282
0
    }
Unexecuted instantiation: js::ProfilingStackFrame::stackAddress() const
Unexecuted instantiation: js::ProfilingStackFrame::stackAddress() const
283
284
    JS_PUBLIC_API(JSScript*) script() const;
285
286
0
    uint32_t line() const {
287
0
        MOZ_ASSERT(!isJsFrame());
288
0
        return static_cast<uint32_t>(lineOrPcOffset);
289
0
    }
Unexecuted instantiation: js::ProfilingStackFrame::line() const
Unexecuted instantiation: js::ProfilingStackFrame::line() const
290
291
    // Note that the pointer returned might be invalid.
292
14
    JSScript* rawScript() const {
293
14
        MOZ_ASSERT(isJsFrame());
294
14
        void* script = spOrScript;
295
14
        return static_cast<JSScript*>(script);
296
14
    }
297
298
    // We can't know the layout of JSScript, so look in vm/GeckoProfiler.cpp.
299
    JS_FRIEND_API(jsbytecode*) pc() const;
300
    void setPC(jsbytecode* pc);
301
302
    void trace(JSTracer* trc);
303
304
    // The offset of a pc into a script's code can actually be 0, so to
305
    // signify a nullptr pc, use a -1 index. This is checked against in
306
    // pc() and setPC() to set/get the right pc.
307
    static const int32_t NullPCOffset = -1;
308
};
309
310
JS_FRIEND_API(void)
311
SetContextProfilingStack(JSContext* cx, ProfilingStack* profilingStack);
312
313
// GetContextProfilingStack also exists, but it's defined in RootingAPI.h.
314
315
JS_FRIEND_API(void)
316
EnableContextProfilingStack(JSContext* cx, bool enabled);
317
318
JS_FRIEND_API(void)
319
RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*));
320
321
} // namespace js
322
323
namespace JS {
324
325
typedef ProfilingStack*
326
(* RegisterThreadCallback)(const char* threadName, void* stackBase);
327
328
typedef void
329
(* UnregisterThreadCallback)();
330
331
JS_FRIEND_API(void)
332
SetProfilingThreadCallbacks(RegisterThreadCallback registerThread,
333
                            UnregisterThreadCallback unregisterThread);
334
335
} // namespace JS
336
337
// Each thread has its own ProfilingStack. That thread modifies the ProfilingStack,
338
// pushing and popping elements as necessary.
339
//
340
// The ProfilingStack is also read periodically by the profiler's sampler thread.
341
// This happens only when the thread that owns the ProfilingStack is suspended.
342
// So there are no genuine parallel accesses.
343
//
344
// However, it is possible for pushing/popping to be interrupted by a periodic
345
// sample. Because of this, we need pushing/popping to be effectively atomic.
346
//
347
// - When pushing a new frame, we increment the stack pointer -- making the new
348
//   frame visible to the sampler thread -- only after the new frame has been
349
//   fully written. The stack pointer is Atomic<uint32_t,ReleaseAcquire>, so
350
//   the increment is a release-store, which ensures that this store is not
351
//   reordered before the writes of the frame.
352
//
353
// - When popping an old frame, the only operation is the decrementing of the
354
//   stack pointer, which is obviously atomic.
355
//
356
class ProfilingStack final
357
{
358
  public:
359
    ProfilingStack()
360
      : stackPointer(0)
361
0
    {}
362
363
    ~ProfilingStack();
364
365
    void pushLabelFrame(const char* label, const char* dynamicString, void* sp,
366
1.62M
                        uint32_t line, js::ProfilingStackFrame::Category category) {
367
1.62M
        uint32_t oldStackPointer = stackPointer;
368
1.62M
369
1.62M
        if (MOZ_LIKELY(capacity > oldStackPointer) || MOZ_LIKELY(ensureCapacitySlow())) {
370
1.62M
            frames[oldStackPointer].initLabelFrame(label, dynamicString, sp, line, category);
371
1.62M
        }
372
1.62M
373
1.62M
        // This must happen at the end! The compiler will not reorder this
374
1.62M
        // update because stackPointer is Atomic<..., ReleaseAcquire>, so any
375
1.62M
        // the writes above will not be reordered below the stackPointer store.
376
1.62M
        // Do the read and the write as two separate statements, in order to
377
1.62M
        // make it clear that we don't need an atomic increment, which would be
378
1.62M
        // more expensive on x86 than the separate operations done here.
379
1.62M
        // This thread is the only one that ever changes the value of
380
1.62M
        // stackPointer.
381
1.62M
        stackPointer = oldStackPointer + 1;
382
1.62M
    }
383
384
1.62M
    void pushSpMarkerFrame(void* sp) {
385
1.62M
        uint32_t oldStackPointer = stackPointer;
386
1.62M
387
1.62M
        if (MOZ_LIKELY(capacity > oldStackPointer) || MOZ_LIKELY(ensureCapacitySlow())) {
388
1.62M
            frames[oldStackPointer].initSpMarkerFrame(sp);
389
1.62M
        }
390
1.62M
391
1.62M
        // This must happen at the end, see the comment in pushLabelFrame.
392
1.62M
        stackPointer = oldStackPointer + 1;
393
1.62M
    }
394
395
    void pushJsFrame(const char* label, const char* dynamicString, JSScript* script,
396
1.62M
                     jsbytecode* pc) {
397
1.62M
        uint32_t oldStackPointer = stackPointer;
398
1.62M
399
1.62M
        if (MOZ_LIKELY(capacity > oldStackPointer) || MOZ_LIKELY(ensureCapacitySlow())) {
400
1.62M
            frames[oldStackPointer].initJsFrame(label, dynamicString, script, pc);
401
1.62M
        }
402
1.62M
403
1.62M
        // This must happen at the end, see the comment in pushLabelFrame.
404
1.62M
        stackPointer = oldStackPointer + 1;
405
1.62M
    }
406
407
4.87M
    void pop() {
408
4.87M
        MOZ_ASSERT(stackPointer > 0);
409
4.87M
        // Do the read and the write as two separate statements, in order to
410
4.87M
        // make it clear that we don't need an atomic decrement, which would be
411
4.87M
        // more expensive on x86 than the separate operations done here.
412
4.87M
        // This thread is the only one that ever changes the value of
413
4.87M
        // stackPointer.
414
4.87M
        uint32_t oldStackPointer = stackPointer;
415
4.87M
        stackPointer = oldStackPointer - 1;
416
4.87M
    }
417
418
    uint32_t stackSize() const { return std::min(uint32_t(stackPointer), stackCapacity()); }
419
    uint32_t stackCapacity() const { return capacity; }
420
421
  private:
422
    // Out of line path for expanding the buffer, since otherwise this would get inlined in every
423
    // DOM WebIDL call.
424
    MOZ_COLD MOZ_MUST_USE bool ensureCapacitySlow();
425
426
    // No copying.
427
    ProfilingStack(const ProfilingStack&) = delete;
428
    void operator=(const ProfilingStack&) = delete;
429
430
    // No moving either.
431
    ProfilingStack(ProfilingStack&&) = delete;
432
    void operator=(ProfilingStack&&) = delete;
433
434
    uint32_t capacity = 0;
435
436
  public:
437
438
    // The pointer to the stack frames, this is read from the profiler thread and written from the
439
    // current thread.
440
    //
441
    // This is effectively a unique pointer.
442
    mozilla::Atomic<js::ProfilingStackFrame*, mozilla::SequentiallyConsistent,
443
                    mozilla::recordreplay::Behavior::DontPreserve> frames { nullptr };
444
445
    // This may exceed the capacity, so instead use the stackSize() method to
446
    // determine the number of valid frames in stackFrames. When this is less
447
    // than stackCapacity(), it refers to the first free stackframe past the top
448
    // of the in-use stack (i.e. frames[stackPointer - 1] is the top stack
449
    // frame).
450
    //
451
    // WARNING WARNING WARNING
452
    //
453
    // This is an atomic variable that uses ReleaseAcquire memory ordering.
454
    // See the "Concurrency considerations" paragraph at the top of this file
455
    // for more details.
456
    mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire,
457
                    mozilla::recordreplay::Behavior::DontPreserve> stackPointer;
458
};
459
460
namespace js {
461
462
class AutoGeckoProfilerEntry;
463
class GeckoProfilerEntryMarker;
464
class GeckoProfilerBaselineOSRMarker;
465
466
class GeckoProfilerThread
467
{
468
    friend class AutoGeckoProfilerEntry;
469
    friend class GeckoProfilerEntryMarker;
470
    friend class GeckoProfilerBaselineOSRMarker;
471
472
    ProfilingStack*         profilingStack_;
473
474
  public:
475
    GeckoProfilerThread();
476
477
0
    uint32_t stackPointer() { MOZ_ASSERT(installed()); return profilingStack_->stackPointer; }
Unexecuted instantiation: js::GeckoProfilerThread::stackPointer()
Unexecuted instantiation: js::GeckoProfilerThread::stackPointer()
478
0
    ProfilingStackFrame* stack() { return profilingStack_->frames; }
479
0
    ProfilingStack* getProfilingStack() { return profilingStack_; }
480
481
    /* management of whether instrumentation is on or off */
482
    bool installed() { return profilingStack_ != nullptr; }
483
484
    void setProfilingStack(ProfilingStack* profilingStack);
485
    void trace(JSTracer* trc);
486
487
    /*
488
     * Functions which are the actual instrumentation to track run information
489
     *
490
     *   - enter: a function has started to execute
491
     *   - updatePC: updates the pc information about where a function
492
     *               is currently executing
493
     *   - exit: this function has ceased execution, and no further
494
     *           entries/exits will be made
495
     */
496
    bool enter(JSContext* cx, JSScript* script, JSFunction* maybeFun);
497
    void exit(JSScript* script, JSFunction* maybeFun);
498
    inline void updatePC(JSContext* cx, JSScript* script, jsbytecode* pc);
499
};
500
501
} // namespace js
502
503
#endif  /* js_ProfilingStack_h */