Skip to content

Commit 03cc841

Browse files
Abseil Teamcopybara-github
authored andcommitted
Use non-stack storage for stack trace buffers
Doing this opportunistically allows us to avoid performance overhead in the vast majority of calls (rare, non-reentrant ones) while simultaneously minimizing stack space usage. PiperOrigin-RevId: 831560881 Change-Id: Idc6ba1dd0dcf1b4aaf3ee7cf468054bcfdcf90af
1 parent e6a6acd commit 03cc841

File tree

8 files changed

+402
-74
lines changed

8 files changed

+402
-74
lines changed

CMake/AbseilDll.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ set(ABSL_INTERNAL_DLL_FILES
128128
"debugging/internal/address_is_readable.h"
129129
"debugging/internal/addresses.h"
130130
"debugging/internal/bounded_utf8_length_sequence.h"
131+
"debugging/internal/borrowed_fixup_buffer.h"
132+
"debugging/internal/borrowed_fixup_buffer.cc"
131133
"debugging/internal/decode_rust_punycode.cc"
132134
"debugging/internal/decode_rust_punycode.h"
133135
"debugging/internal/demangle.cc"

absl/debugging/BUILD.bazel

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@ package(
3535

3636
licenses(["notice"])
3737

38+
cc_library(
39+
name = "borrowed_fixup_buffer",
40+
srcs = ["internal/borrowed_fixup_buffer.cc"],
41+
hdrs = ["internal/borrowed_fixup_buffer.h"],
42+
copts = ABSL_DEFAULT_COPTS,
43+
linkopts = ABSL_DEFAULT_LINKOPTS,
44+
deps = [
45+
"//absl/base:config",
46+
"//absl/base:core_headers",
47+
"//absl/base:malloc_internal",
48+
"//absl/hash",
49+
],
50+
)
51+
52+
cc_test(
53+
name = "borrowed_fixup_buffer_test",
54+
srcs = ["internal/borrowed_fixup_buffer_test.cc"],
55+
copts = ABSL_TEST_COPTS,
56+
linkopts = ABSL_DEFAULT_LINKOPTS,
57+
deps = [
58+
":borrowed_fixup_buffer",
59+
"//absl/base:config",
60+
"@googletest//:gtest",
61+
"@googletest//:gtest_main",
62+
],
63+
)
64+
3865
cc_library(
3966
name = "stacktrace",
4067
srcs = [
@@ -54,6 +81,7 @@ cc_library(
5481
copts = ABSL_DEFAULT_COPTS,
5582
linkopts = ABSL_DEFAULT_LINKOPTS,
5683
deps = [
84+
":borrowed_fixup_buffer",
5785
":debugging_internal",
5886
"//absl/base:config",
5987
"//absl/base:core_headers",
@@ -69,6 +97,7 @@ cc_test(
6997
copts = ABSL_TEST_COPTS,
7098
linkopts = ABSL_DEFAULT_LINKOPTS,
7199
deps = [
100+
":borrowed_fixup_buffer",
72101
":stacktrace",
73102
"//absl/base:config",
74103
"//absl/base:core_headers",
@@ -448,6 +477,7 @@ cc_binary(
448477
":stacktrace",
449478
"//absl/base:config",
450479
"//absl/base:core_headers",
480+
"//absl/cleanup",
451481
"@google_benchmark//:benchmark_main",
452482
],
453483
)

absl/debugging/CMakeLists.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,38 @@
1616

1717
find_library(EXECINFO_LIBRARY execinfo)
1818

19+
absl_cc_library(
20+
NAME
21+
borrowed_fixup_buffer
22+
SRCS
23+
"internal/borrowed_fixup_buffer.cc"
24+
HDRS
25+
"internal/borrowed_fixup_buffer.h"
26+
COPTS
27+
${ABSL_DEFAULT_COPTS}
28+
LINKOPTS
29+
${ABSL_DEFAULT_LINKOPTS}
30+
DEPS
31+
absl::config
32+
absl::core_headers
33+
absl::hash
34+
absl::malloc_internal
35+
PUBLIC
36+
)
37+
38+
absl_cc_test(
39+
NAME
40+
borrowed_fixup_buffer_test
41+
SRCS
42+
"internal/borrowed_fixup_buffer_test.cc"
43+
COPTS
44+
${ABSL_TEST_COPTS}
45+
DEPS
46+
absl::borrowed_fixup_buffer
47+
absl::config
48+
GTest::gmock_main
49+
)
50+
1951
absl_cc_library(
2052
NAME
2153
stacktrace
@@ -38,6 +70,7 @@ absl_cc_library(
3870
LINKOPTS
3971
$<$<BOOL:${EXECINFO_LIBRARY}>:${EXECINFO_LIBRARY}>
4072
DEPS
73+
absl::borrowed_fixup_buffer
4174
absl::debugging_internal
4275
absl::config
4376
absl::core_headers
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 The Abseil Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "absl/debugging/internal/borrowed_fixup_buffer.h"
16+
17+
#include <assert.h>
18+
#include <limits.h>
19+
#include <stddef.h>
20+
#include <stdint.h>
21+
22+
#include <atomic>
23+
#include <iterator>
24+
25+
#include "absl/base/attributes.h"
26+
#include "absl/base/config.h"
27+
#include "absl/base/internal/low_level_alloc.h"
28+
#include "absl/hash/hash.h"
29+
30+
namespace absl {
31+
ABSL_NAMESPACE_BEGIN
32+
namespace internal_stacktrace {
33+
34+
// A buffer for holding fix-up information for stack traces of common sizes_.
35+
struct BorrowedFixupBuffer::FixupStackBuffer {
36+
static constexpr size_t kMaxStackElements = 128; // Can be reduced if needed
37+
std::atomic_flag in_use{};
38+
uintptr_t frames[kMaxStackElements];
39+
int sizes[kMaxStackElements];
40+
41+
ABSL_CONST_INIT static FixupStackBuffer g_instances[kNumStaticBuffers];
42+
};
43+
44+
ABSL_CONST_INIT BorrowedFixupBuffer::FixupStackBuffer
45+
BorrowedFixupBuffer::FixupStackBuffer::g_instances[kNumStaticBuffers] = {};
46+
47+
BorrowedFixupBuffer::~BorrowedFixupBuffer() {
48+
if (borrowed_) {
49+
Unlock();
50+
} else {
51+
base_internal::LowLevelAlloc::Free(frames_);
52+
}
53+
}
54+
55+
BorrowedFixupBuffer::BorrowedFixupBuffer(size_t length) {
56+
FixupStackBuffer* fixup_buffer =
57+
0 < length && length <= FixupStackBuffer::kMaxStackElements ? TryLock()
58+
: nullptr;
59+
borrowed_ = fixup_buffer != nullptr;
60+
if (borrowed_) {
61+
InitViaBorrow(fixup_buffer);
62+
} else {
63+
InitViaAllocation(length);
64+
}
65+
}
66+
67+
void BorrowedFixupBuffer::InitViaBorrow(FixupStackBuffer* borrowed_buffer) {
68+
assert(borrowed_);
69+
frames_ = borrowed_buffer->frames;
70+
sizes_ = borrowed_buffer->sizes;
71+
}
72+
73+
void BorrowedFixupBuffer::InitViaAllocation(size_t length) {
74+
static_assert(alignof(decltype(*frames_)) >= alignof(decltype(*sizes_)),
75+
"contiguous layout assumes decreasing alignment, otherwise "
76+
"padding may be needed in the middle");
77+
assert(!borrowed_);
78+
79+
base_internal::InitSigSafeArena();
80+
void* buf = base_internal::LowLevelAlloc::AllocWithArena(
81+
length * (sizeof(*frames_) + sizeof(*sizes_)),
82+
base_internal::SigSafeArena());
83+
84+
if (buf == nullptr) {
85+
frames_ = nullptr;
86+
sizes_ = nullptr;
87+
return;
88+
}
89+
90+
frames_ = new (buf) uintptr_t[length];
91+
sizes_ = new (static_cast<void*>(static_cast<unsigned char*>(buf) +
92+
length * sizeof(*frames_))) int[length];
93+
}
94+
95+
BorrowedFixupBuffer::FixupStackBuffer* BorrowedFixupBuffer::Find() {
96+
size_t i = absl::Hash<const void*>()(this) %
97+
std::size(FixupStackBuffer::g_instances);
98+
return &FixupStackBuffer::g_instances[i];
99+
}
100+
101+
[[nodiscard]] BorrowedFixupBuffer::FixupStackBuffer*
102+
BorrowedFixupBuffer::TryLock() {
103+
FixupStackBuffer* instance = Find();
104+
// Use memory_order_acquire to ensure that no reads and writes on the borrowed
105+
// buffer are reordered before the borrowing.
106+
return !instance->in_use.test_and_set(std::memory_order_acquire) ? instance
107+
: nullptr;
108+
}
109+
110+
void BorrowedFixupBuffer::Unlock() {
111+
// Use memory_order_release to ensure that no reads and writes on the borrowed
112+
// buffer are reordered after the borrowing.
113+
Find()->in_use.clear(std::memory_order_release);
114+
}
115+
116+
} // namespace internal_stacktrace
117+
ABSL_NAMESPACE_END
118+
} // namespace absl
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2025 The Abseil Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef ABSL_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_
16+
#define ABSL_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_
17+
18+
#include <stddef.h>
19+
#include <stdint.h>
20+
#include <stdlib.h>
21+
22+
#include "absl/base/config.h"
23+
24+
namespace absl {
25+
ABSL_NAMESPACE_BEGIN
26+
namespace internal_stacktrace {
27+
28+
// An RAII type that temporarily acquires a buffer for stack trace fix-ups from
29+
// a pool of preallocated buffers, or attempts to allocate a new buffer if no
30+
// such buffer is available.
31+
// When destroyed, returns the buffer to the pool if it borrowed successfully,
32+
// otherwise deallocates any previously allocated buffer.
33+
class BorrowedFixupBuffer {
34+
public:
35+
static constexpr size_t kNumStaticBuffers = 64;
36+
~BorrowedFixupBuffer();
37+
38+
// The number of frames to allocate space for. Note that allocations can fail.
39+
explicit BorrowedFixupBuffer(size_t length);
40+
41+
uintptr_t* frames() const { return frames_; }
42+
int* sizes() const { return sizes_; }
43+
44+
private:
45+
uintptr_t* frames_;
46+
int* sizes_;
47+
48+
// Have we borrowed a pre-existing buffer (vs. allocated our own)?
49+
bool borrowed_;
50+
51+
struct FixupStackBuffer;
52+
53+
void InitViaBorrow(FixupStackBuffer* borrowed_buffer);
54+
void InitViaAllocation(size_t length);
55+
56+
// Returns a non-null pointer to a buffer that could be potentially borrowed.
57+
FixupStackBuffer* Find();
58+
59+
// Attempts to opportunistically borrow a small buffer in a thread- and
60+
// signal-safe manner. Returns nullptr on failure.
61+
[[nodiscard]] FixupStackBuffer* TryLock();
62+
63+
// Returns the borrowed buffer.
64+
void Unlock();
65+
66+
BorrowedFixupBuffer(const BorrowedFixupBuffer&) = delete;
67+
BorrowedFixupBuffer& operator=(const BorrowedFixupBuffer&) = delete;
68+
};
69+
70+
} // namespace internal_stacktrace
71+
ABSL_NAMESPACE_END
72+
} // namespace absl
73+
74+
#endif // ABSL_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_

0 commit comments

Comments
 (0)