Bug 1559659 - 2. Create, initialize, destroy vector of JSContext* with GlobalHelperThreadState r=jandem

Created a vector of JSContext*, initialized at creation of GlobalHelperThreadState, destroyed when GlobalHelperThreadState.finish() is called. Note that this implementation makes the assumption that the creation and destruction of these objects always happens in the same order (helper thread state created -> main thread context created -> main thread context destroyed -> helper thread state destroyed). In this scenario context checks can clear by each context claiming the main thread during its ctor/dtor but this is only possible if the main thread context is not set at the time.

Also, removed GlobalHelperThreadState::lock() and unlock(), as they are not being used - AutoLockHelperThreadState is used instead.

Differential Revision: https://phabricator.services.mozilla.com/D36842

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Kristen Wright 2019-07-25 18:04:12 +00:00
Родитель d57cc19147
Коммит 027905e475
4 изменённых файлов: 129 добавлений и 53 удалений

Просмотреть файл

@ -65,8 +65,17 @@ GlobalHelperThreadState* gHelperThreadState = nullptr;
bool js::CreateHelperThreadsState() {
MOZ_ASSERT(!gHelperThreadState);
gHelperThreadState = js_new<GlobalHelperThreadState>();
return gHelperThreadState != nullptr;
UniquePtr<GlobalHelperThreadState> helperThreadState =
MakeUnique<GlobalHelperThreadState>();
if (!helperThreadState) {
return false;
}
gHelperThreadState = helperThreadState.release();
if (!gHelperThreadState->initializeHelperContexts()) {
js_delete(gHelperThreadState);
return false;
}
return true;
}
void js::DestroyHelperThreadsState() {
@ -435,6 +444,43 @@ struct MOZ_RAII AutoSetContextParse {
~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); }
};
// We want our default stack size limit to be approximately 2MB, to be safe, but
// expect most threads to use much less. On Linux, however, requesting a stack
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
// on first access, which we do not want. To avoid this possibility, we subtract
// 2 standard VM page sizes from our default.
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
// TSan enforces a minimum stack size that's just slightly larger than our
// default helper stack size. It does this to store blobs of TSan-specific
// data on each thread's stack. Unfortunately, that means that even though
// we'll actually receive a larger stack than we requested, the effective
// usable space of that stack is significantly less than what we expect.
// To offset TSan stealing our stack space from underneath us, double the
// default.
//
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
// require all the thread-specific state that TSan does.
#if defined(MOZ_TSAN)
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
#else
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
#endif
AutoSetHelperThreadContext::AutoSetHelperThreadContext() {
AutoLockHelperThreadState lock;
cx = HelperThreadState().getFirstUnusedContext(lock);
MOZ_ASSERT(cx);
cx->setHelperThread(lock);
cx->nativeStackBase = GetNativeStackBase();
// When we set the JSContext, we need to reset the computed stack limits for
// the current thread, so we also set the native stack quota.
JS_SetNativeStackQuota(cx, HELPER_STACK_QUOTA);
}
static const JSClass parseTaskGlobalClass = {"internal-parse-task-global",
JSCLASS_GLOBAL_FLAGS,
&JS::DefaultGlobalClassOps};
@ -1074,32 +1120,6 @@ bool js::CurrentThreadIsParseThread() {
}
#endif
// We want our default stack size limit to be approximately 2MB, to be safe, but
// expect most threads to use much less. On Linux, however, requesting a stack
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
// on first access, which we do not want. To avoid this possibility, we subtract
// 2 standard VM page sizes from our default.
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
// TSan enforces a minimum stack size that's just slightly larger than our
// default helper stack size. It does this to store blobs of TSan-specific
// data on each thread's stack. Unfortunately, that means that even though
// we'll actually receive a larger stack than we requested, the effective
// usable space of that stack is significantly less than what we expect.
// To offset TSan stealing our stack space from underneath us, double the
// default.
//
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
// require all the thread-specific state that TSan does.
#if defined(MOZ_TSAN)
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
#else
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
#endif
bool GlobalHelperThreadState::ensureInitialized() {
MOZ_ASSERT(CanUseExtraThreads());
@ -1164,6 +1184,7 @@ void GlobalHelperThreadState::finish() {
while (!freeList.empty()) {
jit::FreeIonBuilder(freeList.popCopy());
}
destroyHelperContexts(lock);
}
void GlobalHelperThreadState::finishThreads() {
@ -1178,9 +1199,46 @@ void GlobalHelperThreadState::finishThreads() {
threads.reset(nullptr);
}
void GlobalHelperThreadState::lock() { helperLock.lock(); }
bool GlobalHelperThreadState::initializeHelperContexts() {
AutoLockHelperThreadState lock;
for (size_t i = 0; i < threadCount; i++) {
UniquePtr<JSContext> cx =
js::MakeUnique<JSContext>(nullptr, JS::ContextOptions());
// To initialize context-specific protected data, the context must
// temporarily set itself to the main thread. After initialization,
// cx can clear itself from the thread.
cx->setHelperThread(lock);
if (!cx->init(ContextKind::HelperThread)) {
return false;
}
cx->clearHelperThread(lock);
if (!helperContexts_.append(cx.release())) {
return false;
}
}
return true;
}
void GlobalHelperThreadState::unlock() { helperLock.unlock(); }
JSContext* GlobalHelperThreadState::getFirstUnusedContext(
AutoLockHelperThreadState& locked) {
for (auto& cx : helperContexts_) {
if (cx->contextAvailable(locked)) {
return cx;
}
}
MOZ_CRASH("Expected available JSContext");
}
void GlobalHelperThreadState::destroyHelperContexts(
AutoLockHelperThreadState& lock) {
while (helperContexts_.length() > 0) {
JSContext* cx = helperContexts_.popCopy();
// Before cx can be destroyed, it has to set itself to the main thread.
// This enables it to pass its context-specific data checks.
cx->setHelperThread(lock);
js_delete(cx);
}
}
#ifdef DEBUG
bool GlobalHelperThreadState::isLockedByCurrentThread() const {
@ -1298,8 +1356,8 @@ void GlobalHelperThreadState::triggerFreeUnusedMemory() {
}
AutoLockHelperThreadState lock;
for (auto& thread : *threads) {
thread.shouldFreeUnusedMemory = true;
for (auto& context : helperContexts_) {
context->setFreeUnusedMemory(true);
}
notifyAll(PRODUCER, lock);
}
@ -2532,14 +2590,3 @@ const HelperThread::TaskSpec* HelperThread::findHighestPriorityTask(
return nullptr;
}
void HelperThread::maybeFreeUnusedMemory(JSContext* cx) {
MOZ_ASSERT(idle());
cx->tempLifoAlloc().releaseAll();
if (shouldFreeUnusedMemory) {
cx->tempLifoAlloc().freeAll();
shouldFreeUnusedMemory = false;
}
}

Просмотреть файл

@ -100,6 +100,7 @@ class GlobalHelperThreadState {
typedef Vector<GCParallelTask*, 0, SystemAllocPolicy> GCParallelTaskVector;
typedef Vector<PromiseHelperTask*, 0, SystemAllocPolicy>
PromiseHelperTaskVector;
typedef Vector<JSContext*, 0, SystemAllocPolicy> ContextVector;
// List of available threads, or null if the thread state has not been
// initialized.
@ -146,6 +147,9 @@ class GlobalHelperThreadState {
// GC tasks needing to be done in parallel.
GCParallelTaskVector gcParallelWorklist_;
// Global list of JSContext for GlobalHelperThreadState to use.
ContextVector helperContexts_;
ParseTask* removeFinishedParseTask(ParseTaskKind kind,
JS::OffThreadToken* token);
@ -167,8 +171,10 @@ class GlobalHelperThreadState {
void finish();
void finishThreads();
void lock();
void unlock();
MOZ_MUST_USE bool initializeHelperContexts();
JSContext* getFirstUnusedContext(AutoLockHelperThreadState& locked);
void destroyHelperContexts(AutoLockHelperThreadState& lock);
#ifdef DEBUG
bool isLockedByCurrentThread() const;
#endif
@ -377,12 +383,6 @@ struct HelperThread {
*/
bool terminate;
/*
* Indicates that this thread should free its unused memory when it is next
* idle.
*/
bool shouldFreeUnusedMemory;
/* The current task being executed by this thread, if any. */
mozilla::Maybe<HelperTaskUnion> currentTask;
@ -465,8 +465,6 @@ struct HelperThread {
return nullptr;
}
void maybeFreeUnusedMemory(JSContext* cx);
void handleWasmWorkload(AutoLockHelperThreadState& locked,
wasm::CompileMode mode);
@ -703,6 +701,21 @@ class MOZ_RAII AutoUnlockHelperThreadState : public UnlockGuard<Mutex> {
}
};
struct MOZ_RAII AutoSetHelperThreadContext {
JSContext* cx;
explicit AutoSetHelperThreadContext();
~AutoSetHelperThreadContext() {
AutoLockHelperThreadState lock;
cx->tempLifoAlloc().releaseAll();
if (cx->shouldFreeUnusedMemory()) {
cx->tempLifoAlloc().freeAll();
cx->setFreeUnusedMemory(false);
}
cx->clearHelperThread(lock);
cx = nullptr;
}
};
struct ParseTask : public mozilla::LinkedListElement<ParseTask>,
public JS::OffThreadToken,
public RunnableTask {

Просмотреть файл

@ -1228,6 +1228,7 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options)
freeLists_(this, nullptr),
atomsZoneFreeLists_(this),
defaultFreeOp_(this, runtime, true),
freeUnusedMemory(false),
jitActivation(this, nullptr),
regexpStack(this),
activation_(this, nullptr),

Просмотреть файл

@ -181,6 +181,10 @@ struct JSContext : public JS::RootingContext,
js::ParseTask* parseTask_;
// When a helper thread is using a context, it may need to periodically
// free unused memory.
mozilla::Atomic<bool, mozilla::ReleaseAcquire> freeUnusedMemory;
public:
// This is used by helper threads to change the runtime their context is
// currently operating on.
@ -189,6 +193,17 @@ struct JSContext : public JS::RootingContext,
void setHelperThread(js::AutoLockHelperThreadState& locked);
void clearHelperThread(js::AutoLockHelperThreadState& locked);
bool contextAvailable(js::AutoLockHelperThreadState& locked) {
MOZ_ASSERT(kind_ == js::ContextKind::HelperThread);
return currentThread_ == js::Thread::Id();
}
void setFreeUnusedMemory(bool shouldFree) { freeUnusedMemory = shouldFree; }
bool shouldFreeUnusedMemory() const {
return kind_ == js::ContextKind::HelperThread && freeUnusedMemory;
}
bool isMainThreadContext() const {
return kind_ == js::ContextKind::MainThread;
}