Skip to content

Commit bfd3672

Browse files
committed
[llvm][clang] Allocate a new stack instead of spawning a new thread to get more stack space
Clang spawns a new thread to avoid running out of stack space. This can make debugging and performance analysis more difficult as how the threads are connected is difficult to recover. This patch introduces `runOnNewStack` and applies it in Clang. On platforms that have good support for it this allocates a new stack and moves to it using assembly. Doing split stacks like this actually runs on most platforms, but many debuggers and unwinders reject the large or backwards stack offsets that occur. Apple platforms and tools are known to support this, so this only enables it there for now.
1 parent bfe8523 commit bfd3672

File tree

12 files changed

+238
-30
lines changed

12 files changed

+238
-30
lines changed

clang/include/clang/Basic/Stack.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ namespace clang {
2727

2828
/// Call this once on each thread, as soon after starting the thread as
2929
/// feasible, to note the approximate address of the bottom of the stack.
30-
void noteBottomOfStack();
30+
///
31+
/// \param ForceSet set to true if you know the call is near the bottom of a
32+
/// new stack. Used for split stacks.
33+
void noteBottomOfStack(bool ForceSet = false);
3134

3235
/// Determine whether the stack is nearly exhausted.
3336
bool isStackNearlyExhausted();

clang/lib/Basic/Stack.cpp

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,13 @@
1313

1414
#include "clang/Basic/Stack.h"
1515
#include "llvm/Support/CrashRecoveryContext.h"
16+
#include "llvm/Support/ProgramStack.h"
1617

17-
#ifdef _MSC_VER
18-
#include <intrin.h> // for _AddressOfReturnAddress
19-
#endif
18+
static LLVM_THREAD_LOCAL uintptr_t BottomOfStack = 0;
2019

21-
static LLVM_THREAD_LOCAL void *BottomOfStack = nullptr;
22-
23-
static void *getStackPointer() {
24-
#if __GNUC__ || __has_builtin(__builtin_frame_address)
25-
return __builtin_frame_address(0);
26-
#elif defined(_MSC_VER)
27-
return _AddressOfReturnAddress();
28-
#else
29-
char CharOnStack = 0;
30-
// The volatile store here is intended to escape the local variable, to
31-
// prevent the compiler from optimizing CharOnStack into anything other
32-
// than a char on the stack.
33-
//
34-
// Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19.
35-
char *volatile Ptr = &CharOnStack;
36-
return Ptr;
37-
#endif
38-
}
39-
40-
void clang::noteBottomOfStack() {
41-
if (!BottomOfStack)
42-
BottomOfStack = getStackPointer();
20+
void clang::noteBottomOfStack(bool ForceSet) {
21+
if (!BottomOfStack || ForceSet)
22+
BottomOfStack = llvm::getStackPointer();
4323
}
4424

4525
bool clang::isStackNearlyExhausted() {
@@ -51,7 +31,8 @@ bool clang::isStackNearlyExhausted() {
5131
if (!BottomOfStack)
5232
return false;
5333

54-
intptr_t StackDiff = (intptr_t)getStackPointer() - (intptr_t)BottomOfStack;
34+
intptr_t StackDiff =
35+
(intptr_t)llvm::getStackPointer() - (intptr_t)BottomOfStack;
5536
size_t StackUsage = (size_t)std::abs(StackDiff);
5637

5738
// If the stack pointer has a surprising value, we do not understand this
@@ -66,9 +47,12 @@ bool clang::isStackNearlyExhausted() {
6647
void clang::runWithSufficientStackSpaceSlow(llvm::function_ref<void()> Diag,
6748
llvm::function_ref<void()> Fn) {
6849
llvm::CrashRecoveryContext CRC;
69-
CRC.RunSafelyOnThread([&] {
70-
noteBottomOfStack();
50+
// Preserve the BottomOfStack in case RunSafelyOnNewStack uses split stacks.
51+
uintptr_t PrevBottom = BottomOfStack;
52+
CRC.RunSafelyOnNewStack([&] {
53+
noteBottomOfStack(true);
7154
Diag();
7255
Fn();
7356
}, DesiredStackSize);
57+
BottomOfStack = PrevBottom;
7458
}

clang/lib/Frontend/CompilerInstance.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ compileModuleImpl(CompilerInstance &ImportingInstance, SourceLocation ImportLoc,
12761276

12771277
// Execute the action to actually build the module in-place. Use a separate
12781278
// thread so that we get a stack large enough.
1279-
bool Crashed = !llvm::CrashRecoveryContext().RunSafelyOnThread(
1279+
bool Crashed = !llvm::CrashRecoveryContext().RunSafelyOnNewStack(
12801280
[&]() {
12811281
GenerateModuleFromModuleMapAction Action;
12821282
Instance.ExecuteAction(Action);

llvm/cmake/config-ix.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,23 @@ if (ANDROID OR CYGWIN OR CMAKE_SYSTEM_NAME MATCHES "AIX|DragonFly|FreeBSD|Haiku|
2121
set(HAVE_MACH_MACH_H 0)
2222
set(HAVE_MALLOC_MALLOC_H 0)
2323
set(HAVE_PTHREAD_H 1)
24+
set(HAVE_SYS_RESOURCE_H 1)
2425
set(HAVE_SYS_MMAN_H 1)
2526
set(HAVE_SYSEXITS_H 1)
2627
set(HAVE_UNISTD_H 1)
2728
elseif (APPLE)
2829
set(HAVE_MACH_MACH_H 1)
2930
set(HAVE_MALLOC_MALLOC_H 1)
3031
set(HAVE_PTHREAD_H 1)
32+
set(HAVE_SYS_RESOURCE_H 1)
3133
set(HAVE_SYS_MMAN_H 1)
3234
set(HAVE_SYSEXITS_H 1)
3335
set(HAVE_UNISTD_H 1)
3436
elseif (PURE_WINDOWS)
3537
set(HAVE_MACH_MACH_H 0)
3638
set(HAVE_MALLOC_MALLOC_H 0)
3739
set(HAVE_PTHREAD_H 0)
40+
set(HAVE_SYS_RESOURCE_H 0)
3841
set(HAVE_SYS_MMAN_H 0)
3942
set(HAVE_SYSEXITS_H 0)
4043
set(HAVE_UNISTD_H 0)
@@ -44,6 +47,7 @@ elseif (ZOS)
4447
set(HAVE_MACH_MACH_H 0)
4548
set(HAVE_MALLOC_MALLOC_H 0)
4649
set(HAVE_PTHREAD_H 1)
50+
set(HAVE_SYS_RESOURCE_H 1)
4751
set(HAVE_SYS_MMAN_H 1)
4852
set(HAVE_SYSEXITS_H 0)
4953
set(HAVE_UNISTD_H 1)
@@ -52,6 +56,7 @@ else()
5256
check_include_file(mach/mach.h HAVE_MACH_MACH_H)
5357
check_include_file(malloc/malloc.h HAVE_MALLOC_MALLOC_H)
5458
check_include_file(pthread.h HAVE_PTHREAD_H)
59+
check_include_file(sys/resource.h HAVE_SYS_RESOURCE_H)
5560
check_include_file(sys/mman.h HAVE_SYS_MMAN_H)
5661
check_include_file(sysexits.h HAVE_SYSEXITS_H)
5762
check_include_file(unistd.h HAVE_UNISTD_H)

llvm/include/llvm/Config/config.h.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@
150150
/* Have pthread_rwlock_init */
151151
#cmakedefine HAVE_PTHREAD_RWLOCK_INIT ${HAVE_PTHREAD_RWLOCK_INIT}
152152

153+
/* Define to 1 if you have the <sys/resource.h> header file. */
154+
#cmakedefine HAVE_SYS_RESOURCE_H ${HAVE_SYS_RESOURCE_H}
155+
153156
/* Define to 1 if you have the `sbrk' function. */
154157
#cmakedefine HAVE_SBRK ${HAVE_SBRK}
155158

llvm/include/llvm/Support/CrashRecoveryContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ class CrashRecoveryContext {
9797
return RunSafelyOnThread([&]() { Fn(UserData); }, RequestedStackSize);
9898
}
9999

100+
bool RunSafelyOnNewStack(function_ref<void()>,
101+
unsigned RequestedStackSize = 0);
102+
100103
/// Explicitly trigger a crash recovery in the current process, and
101104
/// return failure from RunSafely(). This function does not return.
102105
[[noreturn]] void HandleExit(int RetCode);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//===--- ProgramStack.h -----------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_PROGRAMSTACK_H
10+
#define LLVM_SUPPORT_PROGRAMSTACK_H
11+
12+
#include "llvm/ADT/STLFunctionalExtras.h"
13+
14+
namespace llvm {
15+
16+
/// \returns an address close to the current value of the stack pointer.
17+
///
18+
/// The value is not guaranteed to point to anything specific. It can be used to
19+
/// estimate how much stack space has been used since the previous call.
20+
uintptr_t getStackPointer();
21+
22+
/// \returns the default stack size for this platform.
23+
///
24+
/// Based on \p RLIMIT_STACK or the equivalent.
25+
unsigned getDefaultStackSize();
26+
27+
/// Runs Fn on a new stack of at least the given size.
28+
///
29+
/// \param StackSize requested stack size. A size of 0 uses the default stack
30+
/// size of the platform.
31+
///
32+
/// The preferred implementation is split stacks on platforms that have a good
33+
/// debugging experience for them. On other platforms a new thread is used.
34+
void runOnNewStack(unsigned StackSize, function_ref<void()> Fn);
35+
36+
template <typename R, typename... Ts>
37+
R runOnNewStack(unsigned StackSize, function_ref<R(Ts...)> Fn, Ts &&...Args) {
38+
std::optional<R> Ret;
39+
runOnNewStack(StackSize, [&]() { Ret = Fn(std::forward<Ts>(Args)...); });
40+
return std::move(*Ret);
41+
}
42+
43+
} // namespace llvm
44+
45+
#endif // LLVM_SUPPORT_PROGRAMSTACK_H

llvm/lib/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ add_llvm_component_library(LLVMSupport
294294
Path.cpp
295295
Process.cpp
296296
Program.cpp
297+
ProgramStack.cpp
297298
RWMutex.cpp
298299
Signals.cpp
299300
Threading.cpp

llvm/lib/Support/CrashRecoveryContext.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "llvm/Config/llvm-config.h"
1111
#include "llvm/Support/ErrorHandling.h"
1212
#include "llvm/Support/ExitCodes.h"
13+
#include "llvm/Support/ProgramStack.h"
1314
#include "llvm/Support/Signals.h"
1415
#include "llvm/Support/thread.h"
1516
#include <cassert>
@@ -523,3 +524,21 @@ bool CrashRecoveryContext::RunSafelyOnThread(function_ref<void()> Fn,
523524
CRC->setSwitchedThread();
524525
return Info.Result;
525526
}
527+
528+
bool CrashRecoveryContext::RunSafelyOnNewStack(function_ref<void()> Fn,
529+
unsigned RequestedStackSize) {
530+
// If crash recovery is disabled, do nothing.
531+
if (gCrashRecoveryEnabled) {
532+
assert(!Impl && "Crash recovery context already initialized!");
533+
CrashRecoveryContextImpl *CRCI = new CrashRecoveryContextImpl(this);
534+
Impl = CRCI;
535+
536+
CRCI->ValidJumpBuffer = true;
537+
if (setjmp(CRCI->JumpBuffer) != 0) {
538+
return false;
539+
}
540+
}
541+
542+
runOnNewStack(RequestedStackSize, Fn);
543+
return true;
544+
}

llvm/lib/Support/ProgramStack.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===--- RunOnNewStack.cpp - Crash Recovery -------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Support/ProgramStack.h"
10+
#include "llvm/Config/config.h"
11+
#include "llvm/Support/Compiler.h"
12+
13+
#ifdef HAVE_SYS_RESOURCE_H
14+
# include <sys/resource.h>
15+
#endif
16+
17+
#ifdef _MSC_VER
18+
# include <intrin.h> // for _AddressOfReturnAddress
19+
#endif
20+
21+
// Currently only Apple AArch64 is known to support split stacks in the debugger
22+
// and other tooling.
23+
#if defined(__APPLE__) && defined(__aarch64__) && \
24+
LLVM_HAS_CPP_ATTRIBUTE(gnu::naked) && __has_extension(gnu_asm)
25+
# define LLVM_HAS_SPLIT_STACKS
26+
# define LLVM_HAS_SPLIT_STACKS_AARCH64
27+
#include <sys/mman.h>
28+
#endif
29+
30+
#ifndef LLVM_HAS_SPLIT_STACKS
31+
# include "llvm/Support/thread.h"
32+
#endif
33+
34+
using namespace llvm;
35+
36+
uintptr_t llvm::getStackPointer() {
37+
#if __GNUC__ || __has_builtin(__builtin_frame_address)
38+
return (uintptr_t)__builtin_frame_address(0);
39+
#elif defined(_MSC_VER)
40+
return (uintptr_t)_AddressOfReturnAddress();
41+
#else
42+
char CharOnStack = 0;
43+
// The volatile store here is intended to escape the local variable, to
44+
// prevent the compiler from optimizing CharOnStack into anything other
45+
// than a char on the stack.
46+
//
47+
// Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19.
48+
char *volatile Ptr = &CharOnStack;
49+
return (uintptr_t)Ptr;
50+
#endif
51+
}
52+
53+
unsigned llvm::getDefaultStackSize() {
54+
#ifdef HAVE_SYS_RESOURCE_H
55+
rlimit RL;
56+
getrlimit(RLIMIT_STACK, &RL);
57+
return RL.rlim_cur;
58+
#else
59+
// 8MiB seems good.
60+
return 8 << 20;
61+
#endif
62+
}
63+
64+
namespace {
65+
#ifdef LLVM_HAS_SPLIT_STACKS_AARCH64
66+
[[gnu::naked]] void runOnNewStackImpl(void *Stack, void (*Fn)(void *),
67+
void *Ctx) {
68+
__asm__ volatile(
69+
"mov x16, sp\n\t"
70+
"sub x0, x0, #0x20\n\t" // subtract space from stack
71+
"stp xzr, x16, [x0, #0x00]\n\t" // save old sp
72+
"stp x29, x30, [x0, #0x10]\n\t" // save fp, lr
73+
"mov sp, x0\n\t" // switch to new stack
74+
"add x29, x0, #0x10\n\t" // switch to new frame
75+
".cfi_def_cfa w29, 16\n\t"
76+
".cfi_offset w30, -8\n\t" // lr
77+
".cfi_offset w29, -16\n\t" // fp
78+
79+
"mov x0, x2\n\t" // Ctx is the only argument
80+
"blr x1\n\t" // call Fn
81+
82+
"ldp x29, x30, [sp, #0x10]\n\t" // restore fp, lr
83+
"ldp xzr, x16, [sp, #0x00]\n\t" // load old sp
84+
"mov sp, x16\n\t"
85+
"ret"
86+
);
87+
}
88+
#endif
89+
90+
#ifdef LLVM_HAS_SPLIT_STACKS
91+
void callback(void *Ctx) {
92+
(*reinterpret_cast<function_ref<void()> *>(Ctx))();
93+
}
94+
#endif
95+
} // namespace
96+
97+
#ifdef LLVM_HAS_SPLIT_STACKS
98+
void llvm::runOnNewStack(unsigned StackSize, function_ref<void()> Fn) {
99+
if (StackSize == 0)
100+
StackSize = getDefaultStackSize();
101+
102+
void *Stack = malloc(StackSize);
103+
void *BottomOfStack = (char *)Stack + StackSize;
104+
105+
runOnNewStackImpl(BottomOfStack, callback, &Fn);
106+
107+
free(Stack);
108+
}
109+
#else
110+
void llvm::runOnNewStack(unsigned StackSize, function_ref<void()> Fn) {
111+
llvm::thread Thread(
112+
StackSize == 0 ? std::nullopt : std::optional<unsigned>(StackSize), Fn);
113+
Thread.join();
114+
}
115+
#endif

llvm/unittests/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ add_llvm_unittest(SupportTests
7070
PerThreadBumpPtrAllocatorTest.cpp
7171
ProcessTest.cpp
7272
ProgramTest.cpp
73+
ProgramStackTest.cpp
7374
RecyclerTest.cpp
7475
RegexTest.cpp
7576
ReverseIterationTest.cpp
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===- unittest/Support/ProgramStackTest.cpp ------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Support/ProgramStack.h"
10+
#include "llvm/Support/Process.h"
11+
#include "gtest/gtest.h"
12+
13+
using namespace llvm;
14+
15+
static uintptr_t func(int &A) {
16+
A = 7;
17+
return getStackPointer();
18+
}
19+
20+
TEST(ProgramStackTest, runOnNewStack) {
21+
int A = 0;
22+
uintptr_t Stack = runOnNewStack(0, function_ref<uintptr_t(int &)>(func), A);
23+
EXPECT_EQ(A, 7);
24+
intptr_t StackDiff = (intptr_t)llvm::getStackPointer() - (intptr_t)Stack;
25+
size_t StackDistance = (size_t)std::abs(StackDiff);
26+
// Page size is used as it's large enough to guarantee were not on the same
27+
// stack but not too large to cause spurious failures.
28+
EXPECT_GT(StackDistance, llvm::sys::Process::getPageSizeEstimate());
29+
}

0 commit comments

Comments
 (0)