Search more aggressively for open slots in absl::internal_stacktrace::BorrowedFixupBuffer

This lets us avoid heap allocations in more situations than before.

PiperOrigin-RevId: 841851984
Change-Id: Ibd16b0b87c250c504c8d1119d9b9eb4031067c28
diff --git a/absl/debugging/internal/borrowed_fixup_buffer.cc b/absl/debugging/internal/borrowed_fixup_buffer.cc
index dae78a7..507a0a2 100644
--- a/absl/debugging/internal/borrowed_fixup_buffer.cc
+++ b/absl/debugging/internal/borrowed_fixup_buffer.cc
@@ -21,6 +21,7 @@
 
 #include <atomic>
 #include <iterator>
+#include <utility>
 
 #include "absl/base/attributes.h"
 #include "absl/base/config.h"
@@ -46,28 +47,27 @@
 
 BorrowedFixupBuffer::~BorrowedFixupBuffer() {
   if (borrowed_) {
-    Unlock();
+    std::move(*this).Unlock();
   } else {
     base_internal::LowLevelAlloc::Free(frames_);
   }
 }
 
-BorrowedFixupBuffer::BorrowedFixupBuffer(size_t length) {
-  FixupStackBuffer* fixup_buffer =
-      0 < length && length <= FixupStackBuffer::kMaxStackElements ? TryLock()
-                                                                  : nullptr;
-  borrowed_ = fixup_buffer != nullptr;
+BorrowedFixupBuffer::BorrowedFixupBuffer(size_t length)
+    : borrowed_(0 < length && length <= FixupStackBuffer::kMaxStackElements
+                    ? TryLock()
+                    : nullptr) {
   if (borrowed_) {
-    InitViaBorrow(fixup_buffer);
+    InitViaBorrow();
   } else {
     InitViaAllocation(length);
   }
 }
 
-void BorrowedFixupBuffer::InitViaBorrow(FixupStackBuffer* borrowed_buffer) {
+void BorrowedFixupBuffer::InitViaBorrow() {
   assert(borrowed_);
-  frames_ = borrowed_buffer->frames;
-  sizes_ = borrowed_buffer->sizes;
+  frames_ = borrowed_->frames;
+  sizes_ = borrowed_->sizes;
 }
 
 void BorrowedFixupBuffer::InitViaAllocation(size_t length) {
@@ -92,25 +92,25 @@
                                    length * sizeof(*frames_))) int[length];
 }
 
-BorrowedFixupBuffer::FixupStackBuffer* BorrowedFixupBuffer::Find() {
-  size_t i = absl::Hash<const void*>()(this) %
-             std::size(FixupStackBuffer::g_instances);
-  return &FixupStackBuffer::g_instances[i];
-}
-
 [[nodiscard]] BorrowedFixupBuffer::FixupStackBuffer*
 BorrowedFixupBuffer::TryLock() {
-  FixupStackBuffer* instance = Find();
-  // Use memory_order_acquire to ensure that no reads and writes on the borrowed
-  // buffer are reordered before the borrowing.
-  return !instance->in_use.test_and_set(std::memory_order_acquire) ? instance
-                                                                   : nullptr;
+  constexpr size_t kNumSlots = std::size(FixupStackBuffer::g_instances);
+  const size_t i = absl::Hash<const void*>()(this) % kNumSlots;
+  for (size_t k = 0; k < kNumSlots; ++k) {
+    auto* instance = &FixupStackBuffer::g_instances[(i + k) % kNumSlots];
+    // Use memory_order_acquire to ensure that no reads and writes on the
+    // borrowed buffer are reordered before the borrowing.
+    if (!instance->in_use.test_and_set(std::memory_order_acquire)) {
+      return instance;
+    }
+  }
+  return nullptr;
 }
 
-void BorrowedFixupBuffer::Unlock() {
+void BorrowedFixupBuffer::Unlock() && {
   // Use memory_order_release to ensure that no reads and writes on the borrowed
   // buffer are reordered after the borrowing.
-  Find()->in_use.clear(std::memory_order_release);
+  borrowed_->in_use.clear(std::memory_order_release);
 }
 
 }  // namespace internal_stacktrace
diff --git a/absl/debugging/internal/borrowed_fixup_buffer.h b/absl/debugging/internal/borrowed_fixup_buffer.h
index c5ea7a3..a8f00c8 100644
--- a/absl/debugging/internal/borrowed_fixup_buffer.h
+++ b/absl/debugging/internal/borrowed_fixup_buffer.h
@@ -42,26 +42,23 @@
   int* sizes() const { return sizes_; }
 
  private:
+  struct FixupStackBuffer;
+
   uintptr_t* frames_;
   int* sizes_;
 
-  // Have we borrowed a pre-existing buffer (vs. allocated our own)?
-  bool borrowed_;
+  // The borrowed pre-existing buffer, if any (if we haven't allocated our own)
+  FixupStackBuffer* const borrowed_;
 
-  struct FixupStackBuffer;
-
-  void InitViaBorrow(FixupStackBuffer* borrowed_buffer);
+  void InitViaBorrow();
   void InitViaAllocation(size_t length);
 
-  // Returns a non-null pointer to a buffer that could be potentially borrowed.
-  FixupStackBuffer* Find();
-
   // Attempts to opportunistically borrow a small buffer in a thread- and
   // signal-safe manner. Returns nullptr on failure.
   [[nodiscard]] FixupStackBuffer* TryLock();
 
   // Returns the borrowed buffer.
-  void Unlock();
+  void Unlock() &&;
 
   BorrowedFixupBuffer(const BorrowedFixupBuffer&) = delete;
   BorrowedFixupBuffer& operator=(const BorrowedFixupBuffer&) = delete;