Skip to content

Commit dcdb166

Browse files
committed
Reland: [llvm][clang] Allocate a new stack instead of spawning a new thread to get more stack space (llvm#136046)
Reland llvm#133173 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 cb4c9cd commit dcdb166

File tree

12 files changed

+264
-30
lines changed

12 files changed

+264
-30
lines changed

clang/docs/ReleaseNotes.rst

+5
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,11 @@ Non-comprehensive list of changes in this release
451451
removed in Clang 20. ``__is_same(__remove_cv(T), decltype(nullptr))`` can be
452452
used instead to check whether a type ``T`` is a ``nullptr``.
453453

454+
- Clang itself now uses split stacks instead of threads for allocating more
455+
stack space when running on Apple AArch64 based platforms. This means that
456+
stack traces of Clang from debuggers, crashes, and profilers may look
457+
different than before.
458+
454459
New Compiler Flags
455460
------------------
456461
- ``-fsanitize=implicit-bitfield-conversion`` checks implicit truncation and

clang/include/clang/Basic/Stack.h

+4-1
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

+12-28
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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1421,7 +1421,7 @@ compileModuleImpl(CompilerInstance &ImportingInstance, SourceLocation ImportLoc,
14211421

14221422
// Execute the action to actually build the module in-place. Use a separate
14231423
// thread so that we get a stack large enough.
1424-
bool Crashed = !llvm::CrashRecoveryContext().RunSafelyOnThread(
1424+
bool Crashed = !llvm::CrashRecoveryContext().RunSafelyOnNewStack(
14251425
[&]() {
14261426
std::unique_ptr<FrontendAction> Action(
14271427
new GenerateModuleFromModuleMapAction);

llvm/include/llvm/Support/CrashRecoveryContext.h

+3
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);
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
// LLVM_HAS_SPLIT_STACKS is exposed in the header because CrashRecoveryContext
15+
// needs to know if it's running on another thread or not.
16+
//
17+
// Currently only Apple AArch64 is known to support split stacks in the debugger
18+
// and other tooling.
19+
#if defined(__APPLE__) && defined(__MACH__) && defined(__aarch64__) && \
20+
__has_extension(gnu_asm)
21+
# define LLVM_HAS_SPLIT_STACKS
22+
# define LLVM_HAS_SPLIT_STACKS_AARCH64
23+
#endif
24+
25+
namespace llvm {
26+
27+
/// \returns an address close to the current value of the stack pointer.
28+
///
29+
/// The value is not guaranteed to point to anything specific. It can be used to
30+
/// estimate how much stack space has been used since the previous call.
31+
uintptr_t getStackPointer();
32+
33+
/// \returns the default stack size for this platform.
34+
///
35+
/// Based on \p RLIMIT_STACK or the equivalent.
36+
unsigned getDefaultStackSize();
37+
38+
/// Runs Fn on a new stack of at least the given size.
39+
///
40+
/// \param StackSize requested stack size. A size of 0 uses the default stack
41+
/// size of the platform.
42+
///
43+
/// The preferred implementation is split stacks on platforms that have a good
44+
/// debugging experience for them. On other platforms a new thread is used.
45+
void runOnNewStack(unsigned StackSize, function_ref<void()> Fn);
46+
47+
template <typename R, typename... Ts>
48+
std::enable_if_t<!std::is_same_v<R, void>, R>
49+
runOnNewStack(unsigned StackSize, function_ref<R(Ts...)> Fn, Ts &&...Args) {
50+
std::optional<R> Ret;
51+
runOnNewStack(StackSize, [&]() { Ret = Fn(std::forward<Ts>(Args)...); });
52+
return std::move(*Ret);
53+
}
54+
55+
template <typename... Ts>
56+
void runOnNewStack(unsigned StackSize, function_ref<void(Ts...)> Fn,
57+
Ts &&...Args) {
58+
runOnNewStack(StackSize, [&]() { Fn(std::forward<Ts>(Args)...); });
59+
}
60+
61+
} // namespace llvm
62+
63+
#endif // LLVM_SUPPORT_PROGRAMSTACK_H

llvm/include/llvm/Support/thread.h

+1
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ namespace this_thread {
211211

212212
#else // !LLVM_ENABLE_THREADS
213213

214+
#include "llvm/Support/ErrorHandling.h"
214215
#include <utility>
215216

216217
namespace llvm {

llvm/lib/Support/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ add_llvm_component_library(LLVMSupport
285285
Path.cpp
286286
Process.cpp
287287
Program.cpp
288+
ProgramStack.cpp
288289
RWMutex.cpp
289290
Signals.cpp
290291
Threading.cpp

llvm/lib/Support/CrashRecoveryContext.cpp

+11
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,13 @@ 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+
#ifdef LLVM_HAS_SPLIT_STACKS
531+
return runOnNewStack(RequestedStackSize,
532+
function_ref<bool()>([&]() { return RunSafely(Fn); }));
533+
#else
534+
return RunSafelyOnThread(Fn, RequestedStackSize);
535+
#endif
536+
}

llvm/lib/Support/ProgramStack.cpp

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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 LLVM_ON_UNIX
14+
# include <sys/resource.h> // for getrlimit
15+
#endif
16+
17+
#ifdef _MSC_VER
18+
# include <intrin.h> // for _AddressOfReturnAddress
19+
#endif
20+
21+
#ifndef LLVM_HAS_SPLIT_STACKS
22+
# include "llvm/Support/thread.h"
23+
#endif
24+
25+
using namespace llvm;
26+
27+
uintptr_t llvm::getStackPointer() {
28+
#if __GNUC__ || __has_builtin(__builtin_frame_address)
29+
return (uintptr_t)__builtin_frame_address(0);
30+
#elif defined(_MSC_VER)
31+
return (uintptr_t)_AddressOfReturnAddress();
32+
#else
33+
volatile char CharOnStack = 0;
34+
// The volatile store here is intended to escape the local variable, to
35+
// prevent the compiler from optimizing CharOnStack into anything other
36+
// than a char on the stack.
37+
//
38+
// Tested on: MSVC 2015 - 2019, GCC 4.9 - 9, Clang 3.2 - 9, ICC 13 - 19.
39+
char *volatile Ptr = &CharOnStack;
40+
return (uintptr_t)Ptr;
41+
#endif
42+
}
43+
44+
unsigned llvm::getDefaultStackSize() {
45+
#ifdef LLVM_ON_UNIX
46+
rlimit RL;
47+
getrlimit(RLIMIT_STACK, &RL);
48+
return RL.rlim_cur;
49+
#else
50+
// Clang recursively parses, instantiates templates, and evaluates constant
51+
// expressions. We've found 8MiB to be a reasonable stack size given the way
52+
// Clang works and the way C++ is commonly written.
53+
return 8 << 20;
54+
#endif
55+
}
56+
57+
// Not an anonymous namespace to avoid warning about undefined local function.
58+
namespace llvm {
59+
#ifdef LLVM_HAS_SPLIT_STACKS_AARCH64
60+
void runOnNewStackImpl(void *Stack, void (*Fn)(void *), void *Ctx) __asm__(
61+
"_ZN4llvm17runOnNewStackImplEPvPFvS0_ES0_");
62+
63+
// This can't use naked functions because there is no way to know if cfi
64+
// directives are being emitted or not.
65+
//
66+
// When adding new platforms it may be better to move to a .S file with macros
67+
// for dealing with platform differences.
68+
__asm__ (
69+
".globl _ZN4llvm17runOnNewStackImplEPvPFvS0_ES0_\n\t"
70+
".p2align 2\n\t"
71+
"_ZN4llvm17runOnNewStackImplEPvPFvS0_ES0_:\n\t"
72+
".cfi_startproc\n\t"
73+
"mov x16, sp\n\t"
74+
"sub x0, x0, #0x20\n\t" // subtract space from stack
75+
"stp xzr, x16, [x0, #0x00]\n\t" // save old sp
76+
"stp x29, x30, [x0, #0x10]\n\t" // save fp, lr
77+
"mov sp, x0\n\t" // switch to new stack
78+
"add x29, x0, #0x10\n\t" // switch to new frame
79+
".cfi_def_cfa w29, 16\n\t"
80+
".cfi_offset w30, -8\n\t" // lr
81+
".cfi_offset w29, -16\n\t" // fp
82+
83+
"mov x0, x2\n\t" // Ctx is the only argument
84+
"blr x1\n\t" // call Fn
85+
86+
"ldp x29, x30, [sp, #0x10]\n\t" // restore fp, lr
87+
"ldp xzr, x16, [sp, #0x00]\n\t" // load old sp
88+
"mov sp, x16\n\t"
89+
"ret\n\t"
90+
".cfi_endproc"
91+
);
92+
#endif
93+
} // namespace llvm
94+
95+
namespace {
96+
#ifdef LLVM_HAS_SPLIT_STACKS
97+
void callback(void *Ctx) {
98+
(*reinterpret_cast<function_ref<void()> *>(Ctx))();
99+
}
100+
#endif
101+
} // namespace
102+
103+
#ifdef LLVM_HAS_SPLIT_STACKS
104+
void llvm::runOnNewStack(unsigned StackSize, function_ref<void()> Fn) {
105+
if (StackSize == 0)
106+
StackSize = getDefaultStackSize();
107+
108+
// We use malloc here instead of mmap because:
109+
// - it's simpler,
110+
// - many malloc implementations will reuse the allocation in cases where
111+
// we're bouncing accross the edge of a stack boundry, and
112+
// - many malloc implemenations will already provide guard pages for
113+
// allocations this large.
114+
void *Stack = malloc(StackSize);
115+
void *BottomOfStack = (char *)Stack + StackSize;
116+
117+
runOnNewStackImpl(BottomOfStack, callback, &Fn);
118+
119+
free(Stack);
120+
}
121+
#else
122+
void llvm::runOnNewStack(unsigned StackSize, function_ref<void()> Fn) {
123+
llvm::thread Thread(
124+
StackSize == 0 ? std::nullopt : std::optional<unsigned>(StackSize), Fn);
125+
Thread.join();
126+
}
127+
#endif

llvm/unittests/Support/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ add_llvm_unittest(SupportTests
6868
PrefixMapperTest.cpp
6969
ProcessTest.cpp
7070
ProgramTest.cpp
71+
ProgramStackTest.cpp
7172
RegexTest.cpp
7273
ReverseIterationTest.cpp
7374
ReplaceFileTest.cpp
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
static void func2(int &A) {
21+
A = 5;
22+
}
23+
24+
TEST(ProgramStackTest, runOnNewStack) {
25+
int A = 0;
26+
uintptr_t Stack = runOnNewStack(0, function_ref<uintptr_t(int &)>(func), A);
27+
EXPECT_EQ(A, 7);
28+
intptr_t StackDiff = (intptr_t)llvm::getStackPointer() - (intptr_t)Stack;
29+
size_t StackDistance = (size_t)std::abs(StackDiff);
30+
// Page size is used as it's large enough to guarantee were not on the same
31+
// stack but not too large to cause spurious failures.
32+
EXPECT_GT(StackDistance, llvm::sys::Process::getPageSizeEstimate());
33+
runOnNewStack(0, function_ref<void(int &)>(func2), A);
34+
EXPECT_EQ(A, 5);
35+
}

0 commit comments

Comments
 (0)