Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/src/vm/GeckoProfiler.cpp
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
#include "vm/GeckoProfiler-inl.h"
8
9
#include "mozilla/DebugOnly.h"
10
11
#include "jsnum.h"
12
13
#include "gc/GC.h"
14
#include "gc/PublicIterators.h"
15
#include "jit/BaselineFrame.h"
16
#include "jit/BaselineJIT.h"
17
#include "jit/JitcodeMap.h"
18
#include "jit/JitFrames.h"
19
#include "jit/JitRealm.h"
20
#include "jit/JSJitFrameIter.h"
21
#include "util/StringBuffer.h"
22
#include "vm/JSScript.h"
23
24
#include "gc/Marking-inl.h"
25
26
using namespace js;
27
28
using mozilla::DebugOnly;
29
30
GeckoProfilerThread::GeckoProfilerThread()
31
  : profilingStack_(nullptr)
32
27
{
33
27
}
34
35
GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt)
36
  : rt(rt),
37
    strings(mutexid::GeckoProfilerStrings),
38
    slowAssertions(false),
39
    enabled_(false),
40
    eventMarker_(nullptr)
41
3
{
42
3
    MOZ_ASSERT(rt != nullptr);
43
3
}
44
45
void
46
GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack)
47
3
{
48
3
    profilingStack_ = profilingStack;
49
3
}
50
51
void
52
GeckoProfilerRuntime::setEventMarker(void (*fn)(const char*))
53
0
{
54
0
    eventMarker_ = fn;
55
0
}
56
57
// Get a pointer to the top-most profiling frame, given the exit frame pointer.
58
static void*
59
GetTopProfilingJitFrame(Activation* act)
60
0
{
61
0
    if (!act || !act->isJit()) {
62
0
        return nullptr;
63
0
    }
64
0
65
0
    jit::JitActivation* jitActivation = act->asJit();
66
0
67
0
    // If there is no exit frame set, just return.
68
0
    if (!jitActivation->hasExitFP()) {
69
0
        return nullptr;
70
0
    }
71
0
72
0
    // Skip wasm frames that might be in the way.
73
0
    OnlyJSJitFrameIter iter(jitActivation);
74
0
    if (iter.done()) {
75
0
        return nullptr;
76
0
    }
77
0
78
0
    jit::JSJitProfilingFrameIterator jitIter((jit::CommonFrameLayout*) iter.frame().fp());
79
0
    MOZ_ASSERT(!jitIter.done());
80
0
    return jitIter.fp();
81
0
}
82
83
void
84
GeckoProfilerRuntime::enable(bool enabled)
85
0
{
86
0
    JSContext* cx = rt->mainContextFromAnyThread();
87
0
    MOZ_ASSERT(cx->geckoProfiler().installed());
88
0
89
0
    if (enabled_ == enabled) {
90
0
        return;
91
0
    }
92
0
93
0
    /*
94
0
     * Ensure all future generated code will be instrumented, or that all
95
0
     * currently instrumented code is discarded
96
0
     */
97
0
    ReleaseAllJITCode(rt->defaultFreeOp());
98
0
99
0
    // This function is called when the Gecko profiler makes a new Sampler
100
0
    // (and thus, a new circular buffer). Set all current entries in the
101
0
    // JitcodeGlobalTable as expired and reset the buffer range start.
102
0
    if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) {
103
0
        rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired();
104
0
    }
105
0
    rt->setProfilerSampleBufferRangeStart(0);
106
0
107
0
    // Ensure that lastProfilingFrame is null for the main thread.
108
0
    if (cx->jitActivation) {
109
0
        cx->jitActivation->setLastProfilingFrame(nullptr);
110
0
        cx->jitActivation->setLastProfilingCallSite(nullptr);
111
0
    }
112
0
113
0
    enabled_ = enabled;
114
0
115
0
    /* Toggle Gecko Profiler-related jumps on baseline jitcode.
116
0
     * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not
117
0
     * jitcode for scripts with active frames on the stack.  These scripts need to have
118
0
     * their profiler state toggled so they behave properly.
119
0
     */
120
0
    jit::ToggleBaselineProfiling(rt, enabled);
121
0
122
0
    /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on
123
0
     * stack.
124
0
     */
125
0
    if (cx->jitActivation) {
126
0
        // Walk through all activations, and set their lastProfilingFrame appropriately.
127
0
        if (enabled) {
128
0
            Activation* act = cx->activation();
129
0
            void* lastProfilingFrame = GetTopProfilingJitFrame(act);
130
0
131
0
            jit::JitActivation* jitActivation = cx->jitActivation;
132
0
            while (jitActivation) {
133
0
                jitActivation->setLastProfilingFrame(lastProfilingFrame);
134
0
                jitActivation->setLastProfilingCallSite(nullptr);
135
0
136
0
                jitActivation = jitActivation->prevJitActivation();
137
0
                lastProfilingFrame = GetTopProfilingJitFrame(jitActivation);
138
0
            }
139
0
        } else {
140
0
            jit::JitActivation* jitActivation = cx->jitActivation;
141
0
            while (jitActivation) {
142
0
                jitActivation->setLastProfilingFrame(nullptr);
143
0
                jitActivation->setLastProfilingCallSite(nullptr);
144
0
                jitActivation = jitActivation->prevJitActivation();
145
0
            }
146
0
        }
147
0
    }
148
0
149
0
    // WebAssembly code does not need to be released, but profiling string
150
0
    // labels have to be generated so that they are available during async
151
0
    // profiling stack iteration.
152
0
    for (RealmsIter r(rt); !r.done(); r.next()) {
153
0
        r->wasm.ensureProfilingLabels(enabled);
154
0
    }
155
0
}
156
157
/* Lookup the string for the function/script, creating one if necessary */
158
const char*
159
GeckoProfilerRuntime::profileString(JSScript* script, JSFunction* maybeFun)
160
0
{
161
0
    auto locked = strings.lock();
162
0
163
0
    ProfileStringMap::AddPtr s = locked->lookupForAdd(script);
164
0
165
0
    if (!s) {
166
0
        auto str = allocProfileString(script, maybeFun);
167
0
        if (!str || !locked->add(s, script, std::move(str))) {
168
0
            return nullptr;
169
0
        }
170
0
    }
171
0
172
0
    return s->value().get();
173
0
}
174
175
void
176
GeckoProfilerRuntime::onScriptFinalized(JSScript* script)
177
0
{
178
0
    /*
179
0
     * This function is called whenever a script is destroyed, regardless of
180
0
     * whether profiling has been turned on, so don't invoke a function on an
181
0
     * invalid hash set. Also, even if profiling was enabled but then turned
182
0
     * off, we still want to remove the string, so no check of enabled() is
183
0
     * done.
184
0
     */
185
0
    auto locked = strings.lock();
186
0
    if (ProfileStringMap::Ptr entry = locked->lookup(script)) {
187
0
        locked->remove(entry);
188
0
    }
189
0
}
190
191
void
192
GeckoProfilerRuntime::markEvent(const char* event)
193
0
{
194
0
    MOZ_ASSERT(enabled());
195
0
    if (eventMarker_) {
196
0
        JS::AutoSuppressGCAnalysis nogc;
197
0
        eventMarker_(event);
198
0
    }
199
0
}
200
201
bool
202
GeckoProfilerThread::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun)
203
0
{
204
0
    const char* dynamicString = cx->runtime()->geckoProfiler().profileString(script, maybeFun);
205
0
    if (dynamicString == nullptr) {
206
0
        ReportOutOfMemory(cx);
207
0
        return false;
208
0
    }
209
0
210
#ifdef DEBUG
211
    // In debug builds, assert the JS profiling stack frames already on the
212
    // stack have a non-null pc. Only look at the top frames to avoid quadratic
213
    // behavior.
214
    uint32_t sp = profilingStack_->stackPointer;
215
    if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) {
216
        size_t start = (sp > 4) ? sp - 4 : 0;
217
        for (size_t i = start; i < sp - 1; i++) {
218
            MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(), profilingStack_->frames[i].pc());
219
        }
220
    }
221
#endif
222
223
0
    profilingStack_->pushJsFrame("", dynamicString, script, script->code());
224
0
    return true;
225
0
}
226
227
void
228
GeckoProfilerThread::exit(JSScript* script, JSFunction* maybeFun)
229
0
{
230
0
    profilingStack_->pop();
231
0
232
#ifdef DEBUG
233
    /* Sanity check to make sure push/pop balanced */
234
    uint32_t sp = profilingStack_->stackPointer;
235
    if (sp < profilingStack_->stackCapacity()) {
236
        JSRuntime* rt = script->runtimeFromMainThread();
237
        const char* dynamicString = rt->geckoProfiler().profileString(script, maybeFun);
238
        /* Can't fail lookup because we should already be in the set */
239
        MOZ_ASSERT(dynamicString);
240
241
        // Bug 822041
242
        if (!profilingStack_->frames[sp].isJsFrame()) {
243
            fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
244
            fprintf(stderr, " frames=%p size=%u/%u\n",
245
                            (void*) profilingStack_->frames,
246
                            uint32_t(profilingStack_->stackPointer),
247
                            profilingStack_->stackCapacity());
248
            for (int32_t i = sp; i >= 0; i--) {
249
                ProfilingStackFrame& frame = profilingStack_->frames[i];
250
                if (frame.isJsFrame()) {
251
                    fprintf(stderr, "  [%d] JS %s\n", i, frame.dynamicString());
252
                } else {
253
                    fprintf(stderr, "  [%d] C line %d %s\n", i, frame.line(), frame.dynamicString());
254
                }
255
            }
256
        }
257
258
        ProfilingStackFrame& frame = profilingStack_->frames[sp];
259
        MOZ_ASSERT(frame.isJsFrame());
260
        MOZ_ASSERT(frame.script() == script);
261
        MOZ_ASSERT(strcmp((const char*) frame.dynamicString(), dynamicString) == 0);
262
    }
263
#endif
264
}
265
266
/*
267
 * Serializes the script/function pair into a "descriptive string" which is
268
 * allowed to fail. This function cannot trigger a GC because it could finalize
269
 * some scripts, resize the hash table of profile strings, and invalidate the
270
 * AddPtr held while invoking allocProfileString.
271
 */
272
UniqueChars
273
GeckoProfilerRuntime::allocProfileString(JSScript* script, JSFunction* maybeFun)
274
0
{
275
0
    // Note: this profiler string is regexp-matched by
276
0
    // devtools/client/profiler/cleopatra/js/parserWorker.js.
277
0
278
0
    // Get the function name, if any.
279
0
    JSAtom* atom = maybeFun ? maybeFun->displayAtom() : nullptr;
280
0
281
0
    // Get the script filename, if any, and its length.
282
0
    const char* filename = script->filename();
283
0
    if (filename == nullptr) {
284
0
        filename = "<unknown>";
285
0
    }
286
0
    size_t lenFilename = strlen(filename);
287
0
288
0
    // Get the line number and its length as a string.
289
0
    uint32_t lineno = script->lineno();
290
0
    size_t lenLineno = 1;
291
0
    for (uint32_t i = lineno; i /= 10; lenLineno++);
292
0
293
0
    // Get the column number and its length as a string.
294
0
    uint32_t column = script->column();
295
0
    size_t lenColumn = 1;
296
0
    for (uint32_t i = column; i /= 10; lenColumn++);
297
0
298
0
    // Determine the required buffer size.
299
0
    size_t len = lenFilename + 1 + lenLineno + 1 + lenColumn; // +1 for each separator colon, ":".
300
0
    if (atom) {
301
0
        len += JS::GetDeflatedUTF8StringLength(atom) + 3; // +3 for the " (" and ")" it adds.
302
0
    }
303
0
304
0
    // Allocate the buffer.
305
0
    UniqueChars cstr(js_pod_malloc<char>(len + 1));
306
0
    if (!cstr) {
307
0
        return nullptr;
308
0
    }
309
0
310
0
    // Construct the descriptive string.
311
0
    DebugOnly<size_t> ret;
312
0
    if (atom) {
313
0
        UniqueChars atomStr = StringToNewUTF8CharsZ(nullptr, *atom);
314
0
        if (!atomStr) {
315
0
            return nullptr;
316
0
        }
317
0
318
0
        ret = snprintf(cstr.get(), len + 1, "%s (%s:%" PRIu32 ":%" PRIu32 ")",
319
0
                atomStr.get(), filename, lineno, column);
320
0
    } else {
321
0
        ret = snprintf(cstr.get(), len + 1, "%s:%" PRIu32 ":%" PRIu32,
322
0
                filename, lineno, column);
323
0
    }
324
0
325
0
    MOZ_ASSERT(ret == len, "Computed length should match actual length!");
326
0
327
0
    return cstr;
328
0
}
329
330
void
331
GeckoProfilerThread::trace(JSTracer* trc)
332
19
{
333
19
    if (profilingStack_) {
334
19
        size_t size = profilingStack_->stackSize();
335
104
        for (size_t i = 0; i < size; i++) {
336
85
            profilingStack_->frames[i].trace(trc);
337
85
        }
338
19
    }
339
19
}
340
341
void
342
GeckoProfilerRuntime::fixupStringsMapAfterMovingGC()
343
0
{
344
0
    auto locked = strings.lock();
345
0
    for (ProfileStringMap::Enum e(locked.get()); !e.empty(); e.popFront()) {
346
0
        JSScript* script = e.front().key();
347
0
        if (IsForwarded(script)) {
348
0
            script = Forwarded(script);
349
0
            e.rekeyFront(script);
350
0
        }
351
0
    }
352
0
}
353
354
#ifdef JSGC_HASH_TABLE_CHECKS
355
void
356
GeckoProfilerRuntime::checkStringsMapAfterMovingGC()
357
{
358
    auto locked = strings.lock();
359
    for (auto r = locked->all(); !r.empty(); r.popFront()) {
360
        JSScript* script = r.front().key();
361
        CheckGCThingAfterMovingGC(script);
362
        auto ptr = locked->lookup(script);
363
        MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
364
    }
365
}
366
#endif
367
368
void
369
ProfilingStackFrame::trace(JSTracer* trc)
370
85
{
371
85
    if (isJsFrame()) {
372
14
        JSScript* s = rawScript();
373
14
        TraceNullableRoot(trc, &s, "ProfilingStackFrame script");
374
14
        spOrScript = s;
375
14
    }
376
85
}
377
378
GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame
379
                                                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
380
    : profiler(&cx->geckoProfiler())
381
1
{
382
1
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
383
1
    if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) {
384
1
        profiler = nullptr;
385
1
        return;
386
1
    }
387
0
388
0
    uint32_t sp = profiler->profilingStack_->stackPointer;
389
0
    if (sp >= profiler->profilingStack_->stackCapacity()) {
390
0
        profiler = nullptr;
391
0
        return;
392
0
    }
393
0
394
0
    spBefore_ = sp;
395
0
    if (sp == 0) {
396
0
        return;
397
0
    }
398
0
399
0
    ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1];
400
0
    MOZ_ASSERT(frame.kind() == ProfilingStackFrame::Kind::JS_NORMAL);
401
0
    frame.setKind(ProfilingStackFrame::Kind::JS_OSR);
402
0
}
403
404
GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker()
405
1
{
406
1
    if (profiler == nullptr) {
407
1
        return;
408
1
    }
409
0
410
0
    uint32_t sp = profiler->stackPointer();
411
0
    MOZ_ASSERT(spBefore_ == sp);
412
0
    if (sp == 0) {
413
0
        return;
414
0
    }
415
0
416
0
    ProfilingStackFrame& frame = profiler->stack()[sp - 1];
417
0
    MOZ_ASSERT(frame.kind() == ProfilingStackFrame::Kind::JS_OSR);
418
0
    frame.setKind(ProfilingStackFrame::Kind::JS_NORMAL);
419
0
}
420
421
JS_PUBLIC_API(JSScript*)
422
ProfilingStackFrame::script() const
423
0
{
424
0
    MOZ_ASSERT(isJsFrame());
425
0
    auto script = reinterpret_cast<JSScript*>(spOrScript.operator void*());
426
0
    if (!script) {
427
0
        return nullptr;
428
0
    }
429
0
430
0
    // If profiling is supressed then we can't trust the script pointers to be
431
0
    // valid as they could be in the process of being moved by a compacting GC
432
0
    // (although it's still OK to get the runtime from them).
433
0
    JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread();
434
0
    if (!cx->isProfilerSamplingEnabled()) {
435
0
        return nullptr;
436
0
    }
437
0
438
0
    MOZ_ASSERT(!IsForwarded(script));
439
0
    return script;
440
0
}
441
442
JS_FRIEND_API(jsbytecode*)
443
ProfilingStackFrame::pc() const
444
0
{
445
0
    MOZ_ASSERT(isJsFrame());
446
0
    if (lineOrPcOffset == NullPCOffset) {
447
0
        return nullptr;
448
0
    }
449
0
450
0
    JSScript* script = this->script();
451
0
    return script ? script->offsetToPC(lineOrPcOffset) : nullptr;
452
0
}
453
454
/* static */ int32_t
455
1.62M
ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) {
456
1.62M
    return aPc ? aScript->pcToOffset(aPc) : NullPCOffset;
457
1.62M
}
458
459
void
460
ProfilingStackFrame::setPC(jsbytecode* pc)
461
0
{
462
0
    MOZ_ASSERT(isJsFrame());
463
0
    JSScript* script = this->script();
464
0
    MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
465
0
    lineOrPcOffset = pcToOffset(script, pc);
466
0
}
467
468
JS_FRIEND_API(void)
469
js::SetContextProfilingStack(JSContext* cx, ProfilingStack* profilingStack)
470
3
{
471
3
    cx->geckoProfiler().setProfilingStack(profilingStack);
472
3
}
473
474
JS_FRIEND_API(void)
475
js::EnableContextProfilingStack(JSContext* cx, bool enabled)
476
0
{
477
0
    cx->runtime()->geckoProfiler().enable(enabled);
478
0
}
479
480
JS_FRIEND_API(void)
481
js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*))
482
0
{
483
0
    MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
484
0
    cx->runtime()->geckoProfiler().setEventMarker(fn);
485
0
}
486
487
AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx
488
                                                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
489
  : cx_(cx),
490
    previouslyEnabled_(cx->isProfilerSamplingEnabled())
491
81
{
492
81
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
493
81
    if (previouslyEnabled_) {
494
81
        cx_->disableProfilerSampling();
495
81
    }
496
81
}
497
498
AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling()
499
81
{
500
81
    if (previouslyEnabled_) {
501
81
        cx_->enableProfilerSampling();
502
81
    }
503
81
}