Commit 5c57c216 authored by Bruce Dawson's avatar Bruce Dawson Committed by Commit Bot

Make the default stack size smaller

Chrome's main thread needs a 1.5 MiB stack size in order to avoid stack
overflow crashes. However if this is set in the PE file then other
threads get this size as well, leading to address-space exhaustion in
32-bit Chrome. For threads that we create we can set the size explicitly
but for some OS threads (the spell checker and those in the Windows
thread pool) they end up with the 1.5 MiB default set in the PE file.

This change tries to control this by setting the default thread size
(in the PE file) to 1.0 MiB and then creating and switching to a fiber
with a 1.5 MiB stack.

This dance wastes 1.0 MiB of address space (for the initial stack that
is never used) but then saves 0.5 MiB of address space for every thread
that doesn't specify a stack size. At startup time on my laptop there
are 13 threads (plus the main thread) with 1.5 MiB stacks, so this
change saves 13*0.5 MiB - 1.0 MiB = 5.5 MiB of address space.

It is quite likely that the default stack size can be dropped to 0.5
MiB which would save another 7 MiB of address space at startup.

Bug: 981238, 1023804
Change-Id: Iabc9d86758a8a9fb77761a5a43a9b11278f4effa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2425743
Commit-Queue: Bruce Dawson <brucedawson@chromium.org>
Reviewed-by: default avatarGreg Thompson <grt@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#813553}
parent 5cb2e5ff
...@@ -211,10 +211,13 @@ if (!is_android && !is_mac) { ...@@ -211,10 +211,13 @@ if (!is_android && !is_mac) {
] ]
if (current_cpu == "x86") { if (current_cpu == "x86") {
# Increase the initial stack size. The default is 1MB, this is 1.5MB. # Set the initial stack size to 1MiB, instead of the 1.5MiB needed by
# To mitigate address space pressure, |CreateThreadInternal| sets the # Chrome's main thread. This saves significant memory on threads (like
# default back to 1MB for non-main threads. # those in the Windows thread pool, and others) whose stack size we can
ldflags = [ "/STACK:0x180000" ] # only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x100000" ]
} else { } else {
# Increase the initial stack size. The default is 1MB, this is 8MB. # Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ] ldflags = [ "/STACK:0x800000" ]
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/at_exit.h" #include "base/at_exit.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/debug/alias.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/path_service.h" #include "base/path_service.h"
...@@ -27,6 +28,7 @@ ...@@ -27,6 +28,7 @@
#include "base/win/registry.h" #include "base/win/registry.h"
#include "base/win/win_util.h" #include "base/win/win_util.h"
#include "base/win/windows_version.h" #include "base/win/windows_version.h"
#include "build/build_config.h"
#include "chrome/app/main_dll_loader_win.h" #include "chrome/app/main_dll_loader_win.h"
#include "chrome/browser/policy/policy_path_parser.h" #include "chrome/browser/policy/policy_path_parser.h"
#include "chrome/browser/win/chrome_process_finder.h" #include "chrome/browser/win/chrome_process_finder.h"
...@@ -158,14 +160,89 @@ int RunFallbackCrashHandler(const base::CommandLine& cmd_line) { ...@@ -158,14 +160,89 @@ int RunFallbackCrashHandler(const base::CommandLine& cmd_line) {
} // namespace } // namespace
namespace {
// In 32-bit builds, the main thread starts with the default (small) stack size.
// The ARCH_CPU_32_BITS blocks here and below are in support of moving the main
// thread to a fiber with a larger stack size.
#if defined(ARCH_CPU_32_BITS)
// The information needed to transfer control to the large-stack fiber and later
// pass the main routine's exit code back to the small-stack fiber prior to
// termination.
struct FiberState {
HINSTANCE instance;
LPVOID original_fiber;
int fiber_result;
};
// A PFIBER_START_ROUTINE function run on a large-stack fiber that calls the
// main routine, stores its return value, and returns control to the small-stack
// fiber. |params| must be a pointer to a FiberState struct.
void WINAPI FiberBinder(void* params) {
auto* fiber_state = static_cast<FiberState*>(params);
// Call the main routine from the fiber. Reusing the entry point minimizes
// confusion when examining call stacks in crash reports - seeing wWinMain on
// the stack is a handy hint that this is the main thread of the process.
#if !defined(WIN_CONSOLE_APP)
fiber_state->fiber_result =
wWinMain(fiber_state->instance, nullptr, nullptr, 0);
#else // !defined(WIN_CONSOLE_APP)
fiber_state->fiber_result = main();
#endif // !defined(WIN_CONSOLE_APP)
// Switch back to the main thread to exit.
::SwitchToFiber(fiber_state->original_fiber);
}
#endif // defined(ARCH_CPU_32_BITS)
} // namespace
#if !defined(WIN_CONSOLE_APP) #if !defined(WIN_CONSOLE_APP)
int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int) { int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE prev, wchar_t*, int) {
#else #else // !defined(WIN_CONSOLE_APP)
int main() { int main() {
HINSTANCE instance = GetModuleHandle(nullptr); HINSTANCE instance = GetModuleHandle(nullptr);
#endif #endif // !defined(WIN_CONSOLE_APP)
#if defined(ARCH_CPU_32_BITS)
enum class FiberStatus { kConvertFailed, kCreateFiberFailed, kSuccess };
FiberStatus fiber_status = FiberStatus::kSuccess;
// GetLastError result if fiber conversion failed.
DWORD fiber_error = ERROR_SUCCESS;
if (!::IsThreadAFiber()) {
constexpr size_t kStackSize = 1536 * 1024; // 1.5 MiB
// Leak the fiber on exit.
LPVOID original_fiber =
::ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH);
if (original_fiber) {
FiberState fiber_state = {instance, original_fiber};
// Create a fiber with a bigger stack and switch to it. Leak the fiber on
// exit.
LPVOID big_stack_fiber = ::CreateFiberEx(
0, kStackSize, FIBER_FLAG_FLOAT_SWITCH, FiberBinder, &fiber_state);
if (big_stack_fiber) {
::SwitchToFiber(big_stack_fiber);
// Control returns here after Chrome has finished running on FiberMain.
return fiber_state.fiber_result;
}
fiber_status = FiberStatus::kCreateFiberFailed;
} else {
fiber_status = FiberStatus::kConvertFailed;
}
// If we reach here then creating and switching to a fiber has failed. This
// probably means we are low on memory and will soon crash. Try to report
// this error once crash reporting is initialized.
fiber_error = ::GetLastError();
base::debug::Alias(&fiber_error);
}
// If we are already a fiber then continue normal execution.
#endif // defined(ARCH_CPU_32_BITS)
install_static::InitializeFromPrimaryModule(); install_static::InitializeFromPrimaryModule();
SignalInitializeCrashReporting(); SignalInitializeCrashReporting();
#if defined(ARCH_CPU_32_BITS)
// Intentionally crash if converting to a fiber failed.
CHECK_EQ(fiber_status, FiberStatus::kSuccess);
#endif // defined(ARCH_CPU_32_BITS)
// Done here to ensure that OOMs that happen early in process initialization // Done here to ensure that OOMs that happen early in process initialization
// are correctly signaled to the OS. // are correctly signaled to the OS.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment