aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h')
-rw-r--r--contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h208
1 files changed, 208 insertions, 0 deletions
diff --git a/contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h b/contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h
new file mode 100644
index 000000000000..0176c40aa899
--- /dev/null
+++ b/contrib/llvm-project/compiler-rt/lib/scudo/standalone/stack_depot.h
@@ -0,0 +1,208 @@
+//===-- stack_depot.h -------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef SCUDO_STACK_DEPOT_H_
+#define SCUDO_STACK_DEPOT_H_
+
+#include "atomic_helpers.h"
+#include "common.h"
+#include "mutex.h"
+
+namespace scudo {
+
+class MurMur2HashBuilder {
+ static const u32 M = 0x5bd1e995;
+ static const u32 Seed = 0x9747b28c;
+ static const u32 R = 24;
+ u32 H;
+
+public:
+ explicit MurMur2HashBuilder(u32 Init = 0) { H = Seed ^ Init; }
+ void add(u32 K) {
+ K *= M;
+ K ^= K >> R;
+ K *= M;
+ H *= M;
+ H ^= K;
+ }
+ u32 get() {
+ u32 X = H;
+ X ^= X >> 13;
+ X *= M;
+ X ^= X >> 15;
+ return X;
+ }
+};
+
+class alignas(atomic_u64) StackDepot {
+ HybridMutex RingEndMu;
+ u32 RingEnd = 0;
+
+ // This data structure stores a stack trace for each allocation and
+ // deallocation when stack trace recording is enabled, that may be looked up
+ // using a hash of the stack trace. The lower bits of the hash are an index
+ // into the Tab array, which stores an index into the Ring array where the
+ // stack traces are stored. As the name implies, Ring is a ring buffer, so a
+ // stack trace may wrap around to the start of the array.
+ //
+ // Each stack trace in Ring is prefixed by a stack trace marker consisting of
+ // a fixed 1 bit in bit 0 (this allows disambiguation between stack frames
+ // and stack trace markers in the case where instruction pointers are 4-byte
+ // aligned, as they are on arm64), the stack trace hash in bits 1-32, and the
+ // size of the stack trace in bits 33-63.
+ //
+ // The insert() function is potentially racy in its accesses to the Tab and
+ // Ring arrays, but find() is resilient to races in the sense that, barring
+ // hash collisions, it will either return the correct stack trace or no stack
+ // trace at all, even if two instances of insert() raced with one another.
+ // This is achieved by re-checking the hash of the stack trace before
+ // returning the trace.
+
+ u32 RingSize = 0;
+ u32 RingMask = 0;
+ u32 TabMask = 0;
+ // This is immediately followed by RingSize atomic_u64 and
+ // (TabMask + 1) atomic_u32.
+
+ atomic_u64 *getRing() {
+ return reinterpret_cast<atomic_u64 *>(reinterpret_cast<char *>(this) +
+ sizeof(StackDepot));
+ }
+
+ atomic_u32 *getTab() {
+ return reinterpret_cast<atomic_u32 *>(reinterpret_cast<char *>(this) +
+ sizeof(StackDepot) +
+ sizeof(atomic_u64) * RingSize);
+ }
+
+ const atomic_u64 *getRing() const {
+ return reinterpret_cast<const atomic_u64 *>(
+ reinterpret_cast<const char *>(this) + sizeof(StackDepot));
+ }
+
+ const atomic_u32 *getTab() const {
+ return reinterpret_cast<const atomic_u32 *>(
+ reinterpret_cast<const char *>(this) + sizeof(StackDepot) +
+ sizeof(atomic_u64) * RingSize);
+ }
+
+public:
+ void init(u32 RingSz, u32 TabSz) {
+ DCHECK(isPowerOfTwo(RingSz));
+ DCHECK(isPowerOfTwo(TabSz));
+ RingSize = RingSz;
+ RingMask = RingSz - 1;
+ TabMask = TabSz - 1;
+ }
+
+ // Ensure that RingSize, RingMask and TabMask are set up in a way that
+ // all accesses are within range of BufSize.
+ bool isValid(uptr BufSize) const {
+ if (!isPowerOfTwo(RingSize))
+ return false;
+ uptr RingBytes = sizeof(atomic_u64) * RingSize;
+ if (RingMask + 1 != RingSize)
+ return false;
+
+ if (TabMask == 0)
+ return false;
+ uptr TabSize = TabMask + 1;
+ if (!isPowerOfTwo(TabSize))
+ return false;
+ uptr TabBytes = sizeof(atomic_u32) * TabSize;
+
+ // Subtract and detect underflow.
+ if (BufSize < sizeof(StackDepot))
+ return false;
+ BufSize -= sizeof(StackDepot);
+ if (BufSize < TabBytes)
+ return false;
+ BufSize -= TabBytes;
+ if (BufSize < RingBytes)
+ return false;
+ return BufSize == RingBytes;
+ }
+
+ // Insert hash of the stack trace [Begin, End) into the stack depot, and
+ // return the hash.
+ u32 insert(uptr *Begin, uptr *End) {
+ auto *Tab = getTab();
+ auto *Ring = getRing();
+
+ MurMur2HashBuilder B;
+ for (uptr *I = Begin; I != End; ++I)
+ B.add(u32(*I) >> 2);
+ u32 Hash = B.get();
+
+ u32 Pos = Hash & TabMask;
+ u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
+ u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
+ u64 Id = (u64(End - Begin) << 33) | (u64(Hash) << 1) | 1;
+ if (Entry == Id)
+ return Hash;
+
+ ScopedLock Lock(RingEndMu);
+ RingPos = RingEnd;
+ atomic_store_relaxed(&Tab[Pos], RingPos);
+ atomic_store_relaxed(&Ring[RingPos], Id);
+ for (uptr *I = Begin; I != End; ++I) {
+ RingPos = (RingPos + 1) & RingMask;
+ atomic_store_relaxed(&Ring[RingPos], *I);
+ }
+ RingEnd = (RingPos + 1) & RingMask;
+ return Hash;
+ }
+
+ // Look up a stack trace by hash. Returns true if successful. The trace may be
+ // accessed via operator[] passing indexes between *RingPosPtr and
+ // *RingPosPtr + *SizePtr.
+ bool find(u32 Hash, uptr *RingPosPtr, uptr *SizePtr) const {
+ auto *Tab = getTab();
+ auto *Ring = getRing();
+
+ u32 Pos = Hash & TabMask;
+ u32 RingPos = atomic_load_relaxed(&Tab[Pos]);
+ if (RingPos >= RingSize)
+ return false;
+ u64 Entry = atomic_load_relaxed(&Ring[RingPos]);
+ u64 HashWithTagBit = (u64(Hash) << 1) | 1;
+ if ((Entry & 0x1ffffffff) != HashWithTagBit)
+ return false;
+ u32 Size = u32(Entry >> 33);
+ if (Size >= RingSize)
+ return false;
+ *RingPosPtr = (RingPos + 1) & RingMask;
+ *SizePtr = Size;
+ MurMur2HashBuilder B;
+ for (uptr I = 0; I != Size; ++I) {
+ RingPos = (RingPos + 1) & RingMask;
+ B.add(u32(atomic_load_relaxed(&Ring[RingPos])) >> 2);
+ }
+ return B.get() == Hash;
+ }
+
+ u64 at(uptr RingPos) const {
+ auto *Ring = getRing();
+ return atomic_load_relaxed(&Ring[RingPos & RingMask]);
+ }
+
+ // This is done for the purpose of fork safety in multithreaded programs and
+ // does not fully disable StackDepot. In particular, find() still works and
+ // only insert() is blocked.
+ void disable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.lock(); }
+
+ void enable() NO_THREAD_SAFETY_ANALYSIS { RingEndMu.unlock(); }
+};
+
+// We need StackDepot to be aligned to 8-bytes so the ring we store after
+// is correctly assigned.
+static_assert(sizeof(StackDepot) % alignof(atomic_u64) == 0);
+
+} // namespace scudo
+
+#endif // SCUDO_STACK_DEPOT_H_