//===-- 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 #include "sanitizer_atomic.h" #include "sanitizer_flat_map.h" #include "sanitizer_internal_defs.h" #include "sanitizer_mutex.h" namespace __sanitizer { template 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 LockBeforeFork(); void UnlockAfterFork(bool fork_child); 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 nodes; friend class StackDepotReverseMap; }; template u32 StackDepotBase::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 u32 StackDepotBase::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 void StackDepotBase::unlock( atomic_uint32_t *p, u32 s) { DCHECK_EQ(s & kLockMask, 0); atomic_store(p, s, memory_order_release); } template u32 StackDepotBase::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 typename StackDepotBase::args_type StackDepotBase::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 void StackDepotBase::LockBeforeFork() { // Do not lock hash table. It's very expensive, but it's not rely needed. The // parent process will neither lock nor unlock. Child process risks to be // deadlocked on already locked buckets. To avoid deadlock we will unlock // every locked buckets in `UnlockAfterFork`. This may affect consistency of // the hash table, but the only issue is a few items inserted by parent // process will be not found by child, and the child may insert them again, // wasting some space in `stackStore`. // We still need to lock nodes. nodes.Lock(); } template void StackDepotBase::UnlockAfterFork( bool fork_child) { nodes.Unlock(); // Only unlock in child process to avoid deadlock. See `LockBeforeFork`. if (!fork_child) return; for (int i = 0; i < kTabSize; ++i) { atomic_uint32_t *p = &tab[i]; uptr s = atomic_load(p, memory_order_relaxed); if (s & kLockMask) unlock(p, s & kUnlockMask); } } template void StackDepotBase::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