Bug 1660458 - Improve TLS initialization and usage, remove inline-able function-static init - r=canaltinova

The profiler should guarantee that TLS initializations are done only once (from `profiler_init()`), so there is no need to potentially do it at every TLS access.
Instead, the initialization functions set the TLS states once (to `Initialized` or `Unavailable`, depending on the underlying TLS initialization success), and later accesses to this effectively-read-only flag can be done without thread synchronization.
This reduces the generated code size.

There are also DEBUG-build checks that no accesses are done before initialization; this is theoretically racy, but it's only used to detect too-early accesses in DEBUG builds, which should never happen anyway.

Differential Revision: https://phabricator.services.mozilla.com/D89338
This commit is contained in:
Gerald Squelart 2020-09-07 23:07:38 +00:00
parent 5abadeda43
commit 5507ddc4c5
2 changed files with 80 additions and 27 deletions

View File

@ -1453,17 +1453,34 @@ Atomic<uint32_t, MemoryOrdering::Relaxed> RacyFeatures::sActiveAndFeatures(0);
// profiling stack used by AutoProfilerLabel.
class TLSRegisteredThread {
public:
static bool Init() {
// Only one call to MOZ_THREAD_LOCAL::init() is needed, we cache the result
// for later calls to Init(), in particular before using get() and set().
static const bool ok = AutoProfilerLabel::ProfilingStackOwnerTLS::Init() &&
sRegisteredThread.init();
return ok;
// This should only be called once before any other access.
// In this case it's called from `profiler_init()` on the main thread, before
// the main thread registers itself.
static void Init() {
MOZ_ASSERT(sState == State::Uninitialized, "Already initialized");
AutoProfilerLabel::ProfilingStackOwnerTLS::Init();
MOZ_ASSERT(
AutoProfilerLabel::ProfilingStackOwnerTLS::sState !=
AutoProfilerLabel::ProfilingStackOwnerTLS::State::Uninitialized,
"Unexpected ProfilingStackOwnerTLS::sState after "
"ProfilingStackOwnerTLS::Init()");
sState =
(AutoProfilerLabel::ProfilingStackOwnerTLS::sState ==
AutoProfilerLabel::ProfilingStackOwnerTLS::State::Initialized &&
sRegisteredThread.init())
? State::Initialized
: State::Unavailable;
}
static bool IsTLSInited() {
MOZ_ASSERT(sState != State::Uninitialized,
"TLSRegisteredThread should only be accessed after Init()");
return sState == State::Initialized;
}
// Get the entire RegisteredThread. Accesses are guarded by gPSMutex.
static class RegisteredThread* RegisteredThread(PSLockRef) {
if (!Init()) {
if (!IsTLSInited()) {
return nullptr;
}
return sRegisteredThread.get();
@ -1471,7 +1488,7 @@ class TLSRegisteredThread {
// Get only the RacyRegisteredThread. Accesses are not guarded by gPSMutex.
static class RacyRegisteredThread* RacyRegisteredThread() {
if (!Init()) {
if (!IsTLSInited()) {
return nullptr;
}
class RegisteredThread* registeredThread = sRegisteredThread.get();
@ -1483,7 +1500,7 @@ class TLSRegisteredThread {
// RacyRegisteredThread() can also be used to get the ProfilingStack, but that
// is marginally slower because it requires an extra pointer indirection.
static ProfilingStack* Stack() {
if (!Init()) {
if (!IsTLSInited()) {
return nullptr;
}
ProfilingStackOwner* profilingStackOwner =
@ -1496,7 +1513,7 @@ class TLSRegisteredThread {
static void SetRegisteredThreadAndAutoProfilerLabelProfilingStack(
PSLockRef, class RegisteredThread* aRegisteredThread) {
if (!Init()) {
if (!IsTLSInited()) {
return;
}
MOZ_RELEASE_ASSERT(
@ -1513,7 +1530,7 @@ class TLSRegisteredThread {
// is kept, because the thread may not have unregistered itself yet, so it may
// still push/pop labels even after the profiler has shut down.
static void ResetRegisteredThread(PSLockRef) {
if (!Init()) {
if (!IsTLSInited()) {
return;
}
sRegisteredThread.set(nullptr);
@ -1522,7 +1539,7 @@ class TLSRegisteredThread {
// Reset the AutoProfilerLabels' ProfilingStack, because the thread is
// unregistering itself.
static void ResetAutoProfilerLabelProfilingStack(PSLockRef) {
if (!Init()) {
if (!IsTLSInited()) {
return;
}
MOZ_RELEASE_ASSERT(
@ -1533,6 +1550,12 @@ class TLSRegisteredThread {
}
private:
// Only written once from `profiler_init` calling
// `TLSRegisteredThread::Init()`; all reads should only happen after `Init()`,
// so there is no need to make it atomic.
enum class State { Uninitialized = 0, Initialized, Unavailable };
static State sState;
// This is a non-owning reference to the RegisteredThread;
// CorePS::mRegisteredThreads is the owning reference. On thread
// deregistration, this reference is cleared and the RegisteredThread is
@ -1540,8 +1563,21 @@ class TLSRegisteredThread {
static MOZ_THREAD_LOCAL(class RegisteredThread*) sRegisteredThread;
};
// Zero-initialized to State::Uninitialized.
/* static */
TLSRegisteredThread::State TLSRegisteredThread::sState;
/* static */
MOZ_THREAD_LOCAL(RegisteredThread*) TLSRegisteredThread::sRegisteredThread;
// Only written once from `profiler_init` (through `TLSRegisteredThread::Init()`
// and `AutoProfilerLabel::ProfilingStackOwnerTLS::Init()`); all reads should
// only happen after `Init()`, so there is no need to make it atomic.
// Zero-initialized to State::Uninitialized.
/* static */
AutoProfilerLabel::ProfilingStackOwnerTLS::State
AutoProfilerLabel::ProfilingStackOwnerTLS::sState;
// Although you can access a thread's ProfilingStack via
// TLSRegisteredThread::sRegisteredThread, we also have a second TLS pointer
// directly to the ProfilingStack. Here's why.
@ -1563,9 +1599,17 @@ MOZ_THREAD_LOCAL(RegisteredThread*) TLSRegisteredThread::sRegisteredThread;
// responsible for destroying the ProfilingStack; Because MOZ_THREAD_LOCAL
// doesn't support RefPtr, AddRef&Release are done explicitly in
// TLSRegisteredThread.
/* static */
MOZ_THREAD_LOCAL(ProfilingStackOwner*)
AutoProfilerLabel::ProfilingStackOwnerTLS::sProfilingStackOwnerTLS;
/* static */
void AutoProfilerLabel::ProfilingStackOwnerTLS::Init() {
MOZ_ASSERT(sState == State::Uninitialized, "Already initialized");
sState =
sProfilingStackOwnerTLS.init() ? State::Initialized : State::Unavailable;
}
void ProfilingStackOwner::DumpStackAndCrash() const {
fprintf(stderr,
"ProfilingStackOwner::DumpStackAndCrash() thread id: %d, size: %u\n",
@ -3768,7 +3812,7 @@ static ProfilingStack* locked_register_thread(PSLockRef aLock,
VTUNE_REGISTER_THREAD(aName);
if (!TLSRegisteredThread::Init()) {
if (!TLSRegisteredThread::IsTLSInited()) {
return nullptr;
}
@ -3923,6 +3967,9 @@ void profiler_init(void* aStackTop) {
PrintUsageThenExit(1); // terminates execution
}
// This must be before any TLS access (e.g.: Thread registration, labels...).
TLSRegisteredThread::Init();
SharedLibraryInfo::Initialize();
uint32_t features = DefaultFeatures() & AvailableFeatures();
@ -5039,7 +5086,7 @@ ProfilingStack* profiler_register_thread(const char* aName,
TextMarkerPayload(text, TimeStamp::NowUnfuzzed()), &lock);
MOZ_RELEASE_ASSERT(
TLSRegisteredThread::Init(),
TLSRegisteredThread::IsTLSInited(),
"Thread should not have already been registered without TLS::Init()");
MOZ_RELEASE_ASSERT(TLSRegisteredThread::RegisteredThread(lock),
"TLS should be set when re-registering thread");
@ -5073,7 +5120,7 @@ void profiler_unregister_thread() {
FindCurrentThreadRegisteredThread(lock);
registeredThread) {
MOZ_RELEASE_ASSERT(
TLSRegisteredThread::Init(),
TLSRegisteredThread::IsTLSInited(),
"Thread should not have been registered without TLS::Init()");
MOZ_RELEASE_ASSERT(TLSRegisteredThread::RegisteredThread(lock),
"TLS should be set when un-registering thread");

View File

@ -1109,6 +1109,8 @@ void profiler_save_profile_to_file(const char* aFilename);
// RAII classes
//---------------------------------------------------------------------------
class TLSRegisteredThread; // Needed for friendship in ProfilingStackOwnerTLS.
namespace mozilla {
class MOZ_RAII AutoProfilerInit {
@ -1261,29 +1263,33 @@ class MOZ_RAII AutoProfilerLabel {
// See the comment on the definition in platform.cpp for details about this.
class ProfilingStackOwnerTLS {
public:
static bool Init() {
// Only one call to MOZ_THREAD_LOCAL::init() is needed, we cache the
// result for later calls to Init(), in particular before using get() and
// set().
static const bool ok = sProfilingStackOwnerTLS.init();
return ok;
}
static ProfilingStackOwner* Get() {
if (!Init()) {
MOZ_ASSERT(
sState != State::Uninitialized,
"ProfilingStackOwnerTLS::Get() should only be called after Init()");
if (sState != State::Initialized) {
return nullptr;
}
return sProfilingStackOwnerTLS.get();
}
static void Set(ProfilingStackOwner* aProfilingStackOwner) {
MOZ_RELEASE_ASSERT(Init(),
"ProfilingStackOwnerTLS::Set() should only be called "
"after a successful Init()");
MOZ_ASSERT(
sState != State::Uninitialized,
"ProfilingStackOwnerTLS::Set() should only be called after Init()");
MOZ_DIAGNOSTIC_ASSERT(sState == State::Initialized,
"ProfilingStackOwnerTLS::Set() should only be "
"called after a successful Init()");
sProfilingStackOwnerTLS.set(aProfilingStackOwner);
}
private:
friend TLSRegisteredThread;
static void Init();
enum class State { Uninitialized = 0, Initialized, Unavailable };
static State sState;
static MOZ_THREAD_LOCAL(ProfilingStackOwner*) sProfilingStackOwnerTLS;
};
};