mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-23 21:46:05 +00:00

Clean-up all of the calls and remove the redundant == 0 checks. There is only one small visible change. For non-Android, the memalign function will now fail if alignment is zero. Before this would have passed.
209 lines
6.5 KiB
C++
209 lines
6.5 KiB
C++
//===-- 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_
|