Skip to content

Commit b4a00f0

Browse files
committed
Bug 1716940 - Pass external thread stack size through to the JS engine r=sfink,bas
This adds plumbing to make the JS engine set the stack quota based on the actual stack size for external thread pool threads (and internal thread pool ones). The quota is calculated as 90% of the size, which is currently hardcoded into the constants. Differential Revision: https://phabricator.services.mozilla.com/D118183
1 parent 0ccfec9 commit b4a00f0

File tree

8 files changed

+57
-40
lines changed

8 files changed

+57
-40
lines changed

js/public/HelperThreadAPI.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace JS {
2121
*/
2222
using HelperThreadTaskCallback = void (*)();
2323
extern JS_PUBLIC_API void SetHelperThreadTaskCallback(
24-
HelperThreadTaskCallback callback, size_t threadCount);
24+
HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize);
2525

2626
// Function to call from external thread pool to run a helper thread task.
2727
extern JS_PUBLIC_API void RunHelperThreadTask();

js/src/vm/HelperThreadState.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ class GlobalHelperThreadState {
8585
// Number of threads to create. May be accessed without locking.
8686
size_t threadCount;
8787

88+
// Thread stack quota to use when running tasks.
89+
size_t stackQuota;
90+
8891
bool terminating_ = false;
8992

9093
typedef Vector<jit::IonCompileTask*, 0, SystemAllocPolicy>
@@ -199,7 +202,8 @@ class GlobalHelperThreadState {
199202
void setCpuCount(size_t count);
200203

201204
void setDispatchTaskCallback(JS::HelperThreadTaskCallback callback,
202-
size_t threadCount);
205+
size_t threadCount, size_t stackSize,
206+
const AutoLockHelperThreadState& lock);
203207

204208
[[nodiscard]] bool ensureContextList(size_t count,
205209
const AutoLockHelperThreadState& lock);

js/src/vm/HelperThreads.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,23 +134,31 @@ void JS::SetProfilingThreadCallbacks(
134134
HelperThreadState().unregisterThread = unregisterThread;
135135
}
136136

137+
static size_t ThreadStackQuotaForSize(size_t size) {
138+
// Set the stack quota to 10% less that the actual size.
139+
return size_t(double(size) * 0.9);
140+
}
141+
137142
// Bug 1630189: Without MOZ_NEVER_INLINE, Windows PGO builds have a linking
138143
// error for HelperThreadTaskCallback.
139144
JS_PUBLIC_API MOZ_NEVER_INLINE void JS::SetHelperThreadTaskCallback(
140-
HelperThreadTaskCallback callback, size_t threadCount) {
141-
HelperThreadState().setDispatchTaskCallback(callback, threadCount);
145+
HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize) {
146+
AutoLockHelperThreadState lock;
147+
HelperThreadState().setDispatchTaskCallback(callback, threadCount, stackSize,
148+
lock);
142149
}
143150

144151
void GlobalHelperThreadState::setDispatchTaskCallback(
145-
JS::HelperThreadTaskCallback callback, size_t threadCount) {
146-
AutoLockHelperThreadState lock;
152+
JS::HelperThreadTaskCallback callback, size_t threadCount, size_t stackSize,
153+
const AutoLockHelperThreadState& lock) {
147154
MOZ_ASSERT(!isInitialized(lock));
148155
MOZ_ASSERT(!dispatchTaskCallback);
149156
MOZ_ASSERT(threadCount != 0);
157+
MOZ_ASSERT(stackSize >= 16 * 1024);
150158

151159
dispatchTaskCallback = callback;
152160
this->threadCount = threadCount;
153-
useInternalThreadPool_ = false;
161+
this->stackQuota = ThreadStackQuotaForSize(stackSize);
154162
}
155163

156164
bool js::StartOffThreadWasmCompile(wasm::CompileTask* task,
@@ -498,7 +506,7 @@ AutoSetHelperThreadContext::AutoSetHelperThreadContext(
498506
cx->setHelperThread(lock);
499507
// When we set the JSContext, we need to reset the computed stack limits for
500508
// the current thread, so we also set the native stack quota.
501-
JS_SetNativeStackQuota(cx, HELPER_STACK_QUOTA);
509+
JS_SetNativeStackQuota(cx, HelperThreadState().stackQuota);
502510
}
503511

504512
AutoSetHelperThreadContext::~AutoSetHelperThreadContext() {
@@ -1300,14 +1308,15 @@ bool GlobalHelperThreadState::ensureInitialized() {
13001308
i = 0;
13011309
}
13021310

1311+
useInternalThreadPool_ = !dispatchTaskCallback;
13031312
if (useInternalThreadPool(lock)) {
13041313
if (!InternalThreadPool::Initialize(threadCount, lock)) {
13051314
return false;
13061315
}
1307-
1308-
dispatchTaskCallback = InternalThreadPool::DispatchTask;
13091316
}
13101317

1318+
MOZ_ASSERT(dispatchTaskCallback);
1319+
13111320
if (!ensureThreadCount(threadCount, lock)) {
13121321
finishThreads(lock);
13131322
return false;

js/src/vm/InternalThreadPool.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@
1414
#include "util/NativeStack.h"
1515
#include "vm/HelperThreadState.h"
1616

17+
// We want our default stack size limit to be approximately 2MB, to be safe, but
18+
// expect most threads to use much less. On Linux, however, requesting a stack
19+
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
20+
// on first access, which we do not want. To avoid this possibility, we subtract
21+
// 2 standard VM page sizes from our default.
22+
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
23+
24+
// TSan enforces a minimum stack size that's just slightly larger than our
25+
// default helper stack size. It does this to store blobs of TSan-specific
26+
// data on each thread's stack. Unfortunately, that means that even though
27+
// we'll actually receive a larger stack than we requested, the effective
28+
// usable space of that stack is significantly less than what we expect.
29+
// To offset TSan stealing our stack space from underneath us, double the
30+
// default.
31+
//
32+
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
33+
// require all the thread-specific state that TSan does.
34+
#if defined(MOZ_TSAN)
35+
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
36+
#else
37+
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
38+
#endif
39+
1740
// These macros are identical in function to the same-named ones in
1841
// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
1942
// use GeckoProfiler.h.
@@ -91,6 +114,8 @@ bool InternalThreadPool::Initialize(size_t threadCount,
91114
}
92115

93116
Instance = instance.release();
117+
HelperThreadState().setDispatchTaskCallback(DispatchTask, threadCount,
118+
HELPER_STACK_SIZE, lock);
94119
return true;
95120
}
96121

js/src/vm/InternalThreadPool.h

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,6 @@
1818
#include "threading/ConditionVariable.h"
1919
#include "threading/ProtectedData.h"
2020

21-
// We want our default stack size limit to be approximately 2MB, to be safe, but
22-
// expect most threads to use much less. On Linux, however, requesting a stack
23-
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
24-
// on first access, which we do not want. To avoid this possibility, we subtract
25-
// 2 standard VM page sizes from our default.
26-
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
27-
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
28-
29-
// TSan enforces a minimum stack size that's just slightly larger than our
30-
// default helper stack size. It does this to store blobs of TSan-specific
31-
// data on each thread's stack. Unfortunately, that means that even though
32-
// we'll actually receive a larger stack than we requested, the effective
33-
// usable space of that stack is significantly less than what we expect.
34-
// To offset TSan stealing our stack space from underneath us, double the
35-
// default.
36-
//
37-
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
38-
// require all the thread-specific state that TSan does.
39-
#if defined(MOZ_TSAN)
40-
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
41-
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
42-
#else
43-
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
44-
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
45-
#endif
46-
4721
namespace js {
4822

4923
class AutoLockHelperThreadState;
@@ -60,15 +34,15 @@ class InternalThreadPool {
6034
static bool IsInitialized() { return Instance; }
6135
static InternalThreadPool& Get();
6236

63-
static void DispatchTask();
64-
6537
bool ensureThreadCount(size_t threadCount, AutoLockHelperThreadState& lock);
6638
size_t threadCount(const AutoLockHelperThreadState& lock);
6739

6840
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
6941
const AutoLockHelperThreadState& lock) const;
7042

7143
private:
44+
static void DispatchTask();
45+
7246
void dispatchTask();
7347
void shutDown(AutoLockHelperThreadState& lock);
7448

js/xpconnect/src/XPCJSContext.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,8 @@ static bool CreateSelfHostedSharedMemory(JSContext* aCx,
11951195
nsresult XPCJSContext::Initialize() {
11961196
if (StaticPrefs::javascript_options_external_thread_pool_DoNotUseDirectly()) {
11971197
size_t threadCount = TaskController::GetPoolThreadCount();
1198-
SetHelperThreadTaskCallback(&DispatchOffThreadTask, threadCount);
1198+
size_t stackSize = TaskController::GetThreadStackSize();
1199+
SetHelperThreadTaskCallback(&DispatchOffThreadTask, threadCount, stackSize);
11991200
}
12001201

12011202
nsresult rv =

xpcom/threads/TaskController.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,14 @@ void TaskController::InitializeThreadPool() {
160160
mPoolThreads.push_back(
161161
{PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index,
162162
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
163-
PR_JOINABLE_THREAD, 512u * 1024u),
163+
PR_JOINABLE_THREAD, sStackSize),
164164
nullptr});
165165
}
166166
}
167167

168+
/* static */
169+
size_t TaskController::GetThreadStackSize() { return sStackSize; }
170+
168171
void TaskController::SetPerformanceCounterState(
169172
PerformanceCounterState* aPerformanceCounterState) {
170173
mPerformanceCounterState = aPerformanceCounterState;

xpcom/threads/TaskController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ class TaskController {
322322
bool MTTaskRunnableProcessedTask() { return mMTTaskRunnableProcessedTask; }
323323

324324
static int32_t GetPoolThreadCount();
325+
static size_t GetThreadStackSize();
325326

326327
private:
327328
friend void ThreadFuncPoolThread(void* aIndex);

0 commit comments

Comments
 (0)