blob: 96d1ddc87fd032d2a6a5746366e652d7231e8213 [file] [log] [blame]
//===-- sanitizer_stackdepotbase.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
//
//===----------------------------------------------------------------------===//
//
// Implementation of a mapping from arbitrary values to unique 32-bit
// identifiers.
//===----------------------------------------------------------------------===//
#ifndef SANITIZER_STACKDEPOTBASE_H
#define SANITIZER_STACKDEPOTBASE_H
#include <stdio.h>
#include "sanitizer_atomic.h"
#include "sanitizer_flat_map.h"
#include "sanitizer_internal_defs.h"
#include "sanitizer_mutex.h"
namespace __sanitizer {
template <class Node, int kReservedBits, int kTabSizeLog>
class StackDepotBase {
static constexpr u32 kIdSizeLog =
sizeof(u32) * 8 - Max(kReservedBits, 1 /* At least 1 bit for locking. */);
static constexpr u32 kNodesSize1Log = kIdSizeLog / 2;
static constexpr u32 kNodesSize2Log = kIdSizeLog - kNodesSize1Log;
static constexpr int kTabSize = 1 << kTabSizeLog; // Hash table size.
static constexpr u32 kUnlockMask = (1ull << kIdSizeLog) - 1;
static constexpr u32 kLockMask = ~kUnlockMask;
public:
typedef typename Node::args_type args_type;
typedef typename Node::handle_type handle_type;
typedef typename Node::hash_type hash_type;
static constexpr u64 kNodesSize1 = 1ull << kNodesSize1Log;
static constexpr u64 kNodesSize2 = 1ull << kNodesSize2Log;
// Maps stack trace to an unique id.
u32 Put(args_type args, bool *inserted = nullptr);
// Retrieves a stored stack trace by the id.
args_type Get(u32 id);
StackDepotStats GetStats() const {
return {
atomic_load_relaxed(&n_uniq_ids),
nodes.MemoryUsage() + Node::allocated(),
};
}
void LockAll();
void UnlockAll();
void PrintAll();
void TestOnlyUnmap() {
nodes.TestOnlyUnmap();
internal_memset(this, 0, sizeof(*this));
}
private:
friend Node;
u32 find(u32 s, args_type args, hash_type hash) const;
static u32 lock(atomic_uint32_t *p);
static void unlock(atomic_uint32_t *p, u32 s);
atomic_uint32_t tab[kTabSize]; // Hash table of Node's.
atomic_uint32_t n_uniq_ids;
TwoLevelMap<Node, kNodesSize1, kNodesSize2> nodes;
friend class StackDepotReverseMap;
};
template <class Node, int kReservedBits, int kTabSizeLog>
u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::find(
u32 s, args_type args, hash_type hash) const {
// Searches linked list s for the stack, returns its id.
for (; s;) {
const Node &node = nodes[s];
if (node.eq(hash, args))
return s;
s = node.link;
}
return 0;
}
template <class Node, int kReservedBits, int kTabSizeLog>
u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::lock(atomic_uint32_t *p) {
// Uses the pointer lsb as mutex.
for (int i = 0;; i++) {
u32 cmp = atomic_load(p, memory_order_relaxed);
if ((cmp & kLockMask) == 0 &&
atomic_compare_exchange_weak(p, &cmp, cmp | kLockMask,
memory_order_acquire))
return cmp;
if (i < 10)
proc_yield(10);
else
internal_sched_yield();
}
}
template <class Node, int kReservedBits, int kTabSizeLog>
void StackDepotBase<Node, kReservedBits, kTabSizeLog>::unlock(
atomic_uint32_t *p, u32 s) {
DCHECK_EQ(s & kLockMask, 0);
atomic_store(p, s, memory_order_release);
}
template <class Node, int kReservedBits, int kTabSizeLog>
u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::Put(args_type args,
bool *inserted) {
if (inserted)
*inserted = false;
if (!LIKELY(Node::is_valid(args)))
return 0;
hash_type h = Node::hash(args);
atomic_uint32_t *p = &tab[h % kTabSize];
u32 v = atomic_load(p, memory_order_consume);
u32 s = v & kUnlockMask;
// First, try to find the existing stack.
u32 node = find(s, args, h);
if (LIKELY(node))
return node;
// If failed, lock, retry and insert new.
u32 s2 = lock(p);
if (s2 != s) {
node = find(s2, args, h);
if (node) {
unlock(p, s2);
return node;
}
}
s = atomic_fetch_add(&n_uniq_ids, 1, memory_order_relaxed) + 1;
CHECK_EQ(s & kUnlockMask, s);
CHECK_EQ(s & (((u32)-1) >> kReservedBits), s);
Node &new_node = nodes[s];
new_node.store(s, args, h);
new_node.link = s2;
unlock(p, s);
if (inserted) *inserted = true;
return s;
}
template <class Node, int kReservedBits, int kTabSizeLog>
typename StackDepotBase<Node, kReservedBits, kTabSizeLog>::args_type
StackDepotBase<Node, kReservedBits, kTabSizeLog>::Get(u32 id) {
if (id == 0)
return args_type();
CHECK_EQ(id & (((u32)-1) >> kReservedBits), id);
if (!nodes.contains(id))
return args_type();
const Node &node = nodes[id];
return node.load(id);
}
template <class Node, int kReservedBits, int kTabSizeLog>
void StackDepotBase<Node, kReservedBits, kTabSizeLog>::LockAll() {
for (int i = 0; i < kTabSize; ++i) {
lock(&tab[i]);
}
}
template <class Node, int kReservedBits, int kTabSizeLog>
void StackDepotBase<Node, kReservedBits, kTabSizeLog>::UnlockAll() {
for (int i = 0; i < kTabSize; ++i) {
atomic_uint32_t *p = &tab[i];
uptr s = atomic_load(p, memory_order_relaxed);
unlock(p, s & kUnlockMask);
}
}
template <class Node, int kReservedBits, int kTabSizeLog>
void StackDepotBase<Node, kReservedBits, kTabSizeLog>::PrintAll() {
for (int i = 0; i < kTabSize; ++i) {
atomic_uint32_t *p = &tab[i];
u32 s = atomic_load(p, memory_order_consume) & kUnlockMask;
for (; s;) {
const Node &node = nodes[s];
Printf("Stack for id %u:\n", s);
node.load(s).Print();
s = node.link;
}
}
}
} // namespace __sanitizer
#endif // SANITIZER_STACKDEPOTBASE_H