Skip to content

Commit b79062c

Browse files
committed
Bug 1436179: Lazily grow the ProfileEntryStorage. r=mstange,jandem
MozReview-Commit-ID: BEGP1ykl4S
1 parent 59d573a commit b79062c

File tree

6 files changed

+107
-24
lines changed

6 files changed

+107
-24
lines changed

config/check_vanilla_allocations.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ def main():
143143
if "Fuzzer" in filename:
144144
continue
145145

146+
# Ignore the profiling pseudo-stack, since it needs to run even when
147+
# SpiderMonkey's allocator isn't initialized.
148+
if "ProfilingStack" in filename:
149+
continue
150+
146151
fn = m.group(2)
147152
if filename == 'jsutil.o':
148153
jsutil_cpp.add(fn)

js/public/ProfilingStack.h

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,20 @@ class ProfileEntry
149149
static int32_t pcToOffset(JSScript* aScript, jsbytecode* aPc);
150150

151151
public:
152+
ProfileEntry() = default;
153+
ProfileEntry& operator=(const ProfileEntry& other)
154+
{
155+
label_ = other.label();
156+
dynamicString_ = other.dynamicString();
157+
void* spScript = other.spOrScript;
158+
spOrScript = spScript;
159+
int32_t offset = other.lineOrPcOffset;
160+
lineOrPcOffset = offset;
161+
uint32_t kindAndCategory = other.kindAndCategory_;
162+
kindAndCategory_ = kindAndCategory;
163+
return *this;
164+
}
165+
152166
enum class Kind : uint32_t {
153167
// A normal C++ frame.
154168
CPP_NORMAL = 0,
@@ -311,18 +325,14 @@ class PseudoStack final
311325
: stackPointer(0)
312326
{}
313327

314-
~PseudoStack() {
315-
// The label macros keep a reference to the PseudoStack to avoid a TLS
316-
// access. If these are somehow not all cleared we will get a
317-
// use-after-free so better to crash now.
318-
MOZ_RELEASE_ASSERT(stackPointer == 0);
319-
}
328+
~PseudoStack();
320329

321330
void pushCppFrame(const char* label, const char* dynamicString, void* sp, uint32_t line,
322331
js::ProfileEntry::Kind kind, js::ProfileEntry::Category category) {
323-
if (stackPointer < MaxEntries) {
324-
entries[stackPointer].initCppFrame(label, dynamicString, sp, line, kind, category);
325-
}
332+
uint32_t oldStackPointer = stackPointer;
333+
334+
if (MOZ_LIKELY(entryCapacity > oldStackPointer) || MOZ_LIKELY(ensureCapacitySlow()))
335+
entries[oldStackPointer].initCppFrame(label, dynamicString, sp, line, kind, category);
326336

327337
// This must happen at the end! The compiler will not reorder this
328338
// update because stackPointer is Atomic<..., ReleaseAcquire>, so any
@@ -332,15 +342,15 @@ class PseudoStack final
332342
// more expensive on x86 than the separate operations done here.
333343
// This thread is the only one that ever changes the value of
334344
// stackPointer.
335-
uint32_t oldStackPointer = stackPointer;
336345
stackPointer = oldStackPointer + 1;
337346
}
338347

339348
void pushJsFrame(const char* label, const char* dynamicString, JSScript* script,
340349
jsbytecode* pc) {
341-
if (stackPointer < MaxEntries) {
342-
entries[stackPointer].initJsFrame(label, dynamicString, script, pc);
343-
}
350+
uint32_t oldStackPointer = stackPointer;
351+
352+
if (MOZ_LIKELY(entryCapacity > oldStackPointer) || MOZ_LIKELY(ensureCapacitySlow()))
353+
entries[oldStackPointer].initJsFrame(label, dynamicString, script, pc);
344354

345355
// This must happen at the end! The compiler will not reorder this
346356
// update because stackPointer is Atomic<..., ReleaseAcquire>, which
@@ -351,7 +361,6 @@ class PseudoStack final
351361
// more expensive on x86 than the separate operations done here.
352362
// This thread is the only one that ever changes the value of
353363
// stackPointer.
354-
uint32_t oldStackPointer = stackPointer;
355364
stackPointer = oldStackPointer + 1;
356365
}
357366

@@ -366,20 +375,33 @@ class PseudoStack final
366375
stackPointer = oldStackPointer - 1;
367376
}
368377

369-
uint32_t stackSize() const { return std::min(uint32_t(stackPointer), uint32_t(MaxEntries)); }
378+
uint32_t stackSize() const { return std::min(uint32_t(stackPointer), stackCapacity()); }
379+
uint32_t stackCapacity() const { return entryCapacity; }
370380

371381
private:
382+
// Out of line path for expanding the buffer, since otherwise this would get inlined in every
383+
// DOM WebIDL call.
384+
MOZ_COLD MOZ_MUST_USE bool ensureCapacitySlow();
385+
372386
// No copying.
373387
PseudoStack(const PseudoStack&) = delete;
374388
void operator=(const PseudoStack&) = delete;
375389

390+
// No moving either.
391+
PseudoStack(PseudoStack&&) = delete;
392+
void operator=(PseudoStack&&) = delete;
393+
394+
uint32_t entryCapacity = 0;
395+
376396
public:
377-
static const uint32_t MaxEntries = 1024;
378397

379-
// The stack entries.
380-
js::ProfileEntry entries[MaxEntries];
398+
// The pointer to the stack entries, this is read from the profiler thread and written from the
399+
// current thread.
400+
//
401+
// This is effectively a unique pointer.
402+
mozilla::Atomic<js::ProfileEntry*> entries { nullptr };
381403

382-
// This may exceed MaxEntries, so instead use the stackSize() method to
404+
// This may exceed the entry capacity, so instead use the stackSize() method to
383405
// determine the number of valid samples in entries. When this is less
384406
// than MaxEntries, it refers to the first free entry past the top of the
385407
// in-use stack (i.e. entries[stackPointer - 1] is the top stack entry).

js/src/moz.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,9 @@ UNIFIED_SOURCES += [
440440
# StoreBuffer.cpp cannot be built in unified because its template
441441
# instantiations may or may not be needed depending on what it gets bundled
442442
# with.
443+
# perf/ProfilingStack.cpp cannot be built in unified mode because we want to
444+
# suppress warnings due to usage of the system allocator, and this allows it
445+
# to have a deterministic object name.
443446
# util/DoubleToString.cpp cannot be built in unified mode because we want to
444447
# suppress compiler warnings in third-party dtoa.c.
445448
# vm/Interpreter.cpp is gigantic and destroys incremental build times for any
@@ -454,6 +457,7 @@ SOURCES += [
454457
'util/DoubleToString.cpp',
455458
'vm/Interpreter.cpp',
456459
'vm/JSAtom.cpp',
460+
'vm/ProfilingStack.cpp',
457461
]
458462

459463
if CONFIG['JS_POSIX_NSPR']:

js/src/vm/GeckoProfiler-inl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ GeckoProfilerThread::updatePC(JSContext* cx, JSScript* script, jsbytecode* pc)
2121
return;
2222

2323
uint32_t sp = pseudoStack_->stackPointer;
24-
if (sp - 1 < PseudoStack::MaxEntries) {
24+
if (sp - 1 < pseudoStack_->stackCapacity()) {
2525
MOZ_ASSERT(sp > 0);
2626
MOZ_ASSERT(pseudoStack_->entries[sp - 1].rawScript() == script);
2727
pseudoStack_->entries[sp - 1].setPC(pc);

js/src/vm/GeckoProfiler.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ GeckoProfilerThread::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun
215215
// have a non-null pc. Only look at the top frames to avoid quadratic
216216
// behavior.
217217
uint32_t sp = pseudoStack_->stackPointer;
218-
if (sp > 0 && sp - 1 < PseudoStack::MaxEntries) {
218+
if (sp > 0 && sp - 1 < pseudoStack_->stackCapacity()) {
219219
size_t start = (sp > 4) ? sp - 4 : 0;
220220
for (size_t i = start; i < sp - 1; i++)
221221
MOZ_ASSERT_IF(pseudoStack_->entries[i].isJs(), pseudoStack_->entries[i].pc());
@@ -234,7 +234,7 @@ GeckoProfilerThread::exit(JSScript* script, JSFunction* maybeFun)
234234
#ifdef DEBUG
235235
/* Sanity check to make sure push/pop balanced */
236236
uint32_t sp = pseudoStack_->stackPointer;
237-
if (sp < PseudoStack::MaxEntries) {
237+
if (sp < pseudoStack_->stackCapacity()) {
238238
JSRuntime* rt = script->runtimeFromActiveCooperatingThread();
239239
const char* dynamicString = rt->geckoProfiler().profileString(script, maybeFun);
240240
/* Can't fail lookup because we should already be in the set */
@@ -246,7 +246,7 @@ GeckoProfilerThread::exit(JSScript* script, JSFunction* maybeFun)
246246
fprintf(stderr, " entries=%p size=%u/%u\n",
247247
(void*) pseudoStack_->entries,
248248
uint32_t(pseudoStack_->stackPointer),
249-
PseudoStack::MaxEntries);
249+
pseudoStack_->stackCapacity());
250250
for (int32_t i = sp; i >= 0; i--) {
251251
ProfileEntry& entry = pseudoStack_->entries[i];
252252
if (entry.isJs())
@@ -382,7 +382,7 @@ GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSContext* cx, bo
382382
}
383383

384384
uint32_t sp = profiler->pseudoStack_->stackPointer;
385-
if (sp >= PseudoStack::MaxEntries) {
385+
if (sp >= profiler->pseudoStack_->stackCapacity()) {
386386
profiler = nullptr;
387387
return;
388388
}

js/src/vm/ProfilingStack.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 "js/ProfilingStack.h"
8+
9+
#include "mozilla/IntegerRange.h"
10+
#include "mozilla/UniquePtr.h"
11+
#include "mozilla/UniquePtrExtensions.h"
12+
13+
#include <algorithm>
14+
15+
using namespace js;
16+
17+
PseudoStack::~PseudoStack()
18+
{
19+
// The label macros keep a reference to the PseudoStack to avoid a TLS
20+
// access. If these are somehow not all cleared we will get a
21+
// use-after-free so better to crash now.
22+
MOZ_RELEASE_ASSERT(stackPointer == 0);
23+
24+
delete[] entries;
25+
}
26+
27+
bool
28+
PseudoStack::ensureCapacitySlow()
29+
{
30+
MOZ_ASSERT(stackPointer >= entryCapacity);
31+
const uint32_t kInitialCapacity = 128;
32+
33+
uint32_t sp = stackPointer;
34+
auto newCapacity = std::max(sp + 1, entryCapacity ? entryCapacity * 2 : kInitialCapacity);
35+
36+
auto* newEntries =
37+
new (mozilla::fallible) js::ProfileEntry[newCapacity];
38+
if (MOZ_UNLIKELY(!newEntries))
39+
return false;
40+
41+
// It's important that `entries` / `entryCapacity` / `stackPointer` remain consistent here at
42+
// all times.
43+
for (auto i : mozilla::IntegerRange(entryCapacity))
44+
newEntries[i] = entries[i];
45+
46+
js::ProfileEntry* oldEntries = entries;
47+
entries = newEntries;
48+
entryCapacity = newCapacity;
49+
delete[] oldEntries;
50+
51+
return true;
52+
}

0 commit comments

Comments
 (0)