1 | //===-- sanitizer_stackdepotbase.h ------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Implementation of a mapping from arbitrary values to unique 32-bit |
10 | // identifiers. |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #ifndef SANITIZER_STACKDEPOTBASE_H |
14 | #define SANITIZER_STACKDEPOTBASE_H |
15 | |
16 | #include <stdio.h> |
17 | |
18 | #include "sanitizer_atomic.h" |
19 | #include "sanitizer_flat_map.h" |
20 | #include "sanitizer_internal_defs.h" |
21 | #include "sanitizer_mutex.h" |
22 | |
23 | namespace __sanitizer { |
24 | |
25 | template <class Node, int kReservedBits, int kTabSizeLog> |
26 | class StackDepotBase { |
27 | static constexpr u32 kIdSizeLog = |
28 | sizeof(u32) * 8 - Max(a: kReservedBits, b: 1 /* At least 1 bit for locking. */); |
29 | static constexpr u32 kNodesSize1Log = kIdSizeLog / 2; |
30 | static constexpr u32 kNodesSize2Log = kIdSizeLog - kNodesSize1Log; |
31 | static constexpr int kTabSize = 1 << kTabSizeLog; // Hash table size. |
32 | static constexpr u32 kUnlockMask = (1ull << kIdSizeLog) - 1; |
33 | static constexpr u32 kLockMask = ~kUnlockMask; |
34 | |
35 | public: |
36 | typedef typename Node::args_type args_type; |
37 | typedef typename Node::handle_type handle_type; |
38 | typedef typename Node::hash_type hash_type; |
39 | |
40 | static constexpr u64 kNodesSize1 = 1ull << kNodesSize1Log; |
41 | static constexpr u64 kNodesSize2 = 1ull << kNodesSize2Log; |
42 | |
43 | // Maps stack trace to an unique id. |
44 | u32 Put(args_type args, bool *inserted = nullptr); |
45 | // Retrieves a stored stack trace by the id. |
46 | args_type Get(u32 id); |
47 | |
48 | StackDepotStats GetStats() const { |
49 | return { |
50 | atomic_load_relaxed(a: &n_uniq_ids), |
51 | nodes.MemoryUsage() + Node::allocated(), |
52 | }; |
53 | } |
54 | |
55 | void LockBeforeFork(); |
56 | void UnlockAfterFork(bool fork_child); |
57 | void PrintAll(); |
58 | |
59 | void TestOnlyUnmap() { |
60 | nodes.TestOnlyUnmap(); |
61 | internal_memset(this, 0, sizeof(*this)); |
62 | } |
63 | |
64 | private: |
65 | friend Node; |
66 | u32 find(u32 s, args_type args, hash_type hash) const; |
67 | static u32 lock(atomic_uint32_t *p); |
68 | static void unlock(atomic_uint32_t *p, u32 s); |
69 | atomic_uint32_t tab[kTabSize]; // Hash table of Node's. |
70 | |
71 | atomic_uint32_t n_uniq_ids; |
72 | |
73 | TwoLevelMap<Node, kNodesSize1, kNodesSize2> nodes; |
74 | |
75 | friend class StackDepotReverseMap; |
76 | }; |
77 | |
78 | template <class Node, int kReservedBits, int kTabSizeLog> |
79 | u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::find( |
80 | u32 s, args_type args, hash_type hash) const { |
81 | // Searches linked list s for the stack, returns its id. |
82 | for (; s;) { |
83 | const Node &node = nodes[s]; |
84 | if (node.eq(hash, args)) |
85 | return s; |
86 | s = node.link; |
87 | } |
88 | return 0; |
89 | } |
90 | |
91 | template <class Node, int kReservedBits, int kTabSizeLog> |
92 | u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::lock(atomic_uint32_t *p) { |
93 | // Uses the pointer lsb as mutex. |
94 | for (int i = 0;; i++) { |
95 | u32 cmp = atomic_load(a: p, mo: memory_order_relaxed); |
96 | if ((cmp & kLockMask) == 0 && |
97 | atomic_compare_exchange_weak(a: p, cmp: &cmp, xchg: cmp | kLockMask, |
98 | mo: memory_order_acquire)) |
99 | return cmp; |
100 | if (i < 10) |
101 | proc_yield(cnt: 10); |
102 | else |
103 | internal_sched_yield(); |
104 | } |
105 | } |
106 | |
107 | template <class Node, int kReservedBits, int kTabSizeLog> |
108 | void StackDepotBase<Node, kReservedBits, kTabSizeLog>::unlock( |
109 | atomic_uint32_t *p, u32 s) { |
110 | DCHECK_EQ(s & kLockMask, 0); |
111 | atomic_store(a: p, v: s, mo: memory_order_release); |
112 | } |
113 | |
114 | template <class Node, int kReservedBits, int kTabSizeLog> |
115 | u32 StackDepotBase<Node, kReservedBits, kTabSizeLog>::Put(args_type args, |
116 | bool *inserted) { |
117 | if (inserted) |
118 | *inserted = false; |
119 | if (!LIKELY(Node::is_valid(args))) |
120 | return 0; |
121 | hash_type h = Node::hash(args); |
122 | atomic_uint32_t *p = &tab[h % kTabSize]; |
123 | u32 v = atomic_load(a: p, mo: memory_order_consume); |
124 | u32 s = v & kUnlockMask; |
125 | // First, try to find the existing stack. |
126 | u32 node = find(s, args, hash: h); |
127 | if (LIKELY(node)) |
128 | return node; |
129 | |
130 | // If failed, lock, retry and insert new. |
131 | u32 s2 = lock(p); |
132 | if (s2 != s) { |
133 | node = find(s: s2, args, hash: h); |
134 | if (node) { |
135 | unlock(p, s: s2); |
136 | return node; |
137 | } |
138 | } |
139 | s = atomic_fetch_add(a: &n_uniq_ids, v: 1, mo: memory_order_relaxed) + 1; |
140 | CHECK_EQ(s & kUnlockMask, s); |
141 | CHECK_EQ(s & (((u32)-1) >> kReservedBits), s); |
142 | Node &new_node = nodes[s]; |
143 | new_node.store(s, args, h); |
144 | new_node.link = s2; |
145 | unlock(p, s); |
146 | if (inserted) *inserted = true; |
147 | return s; |
148 | } |
149 | |
150 | template <class Node, int kReservedBits, int kTabSizeLog> |
151 | typename StackDepotBase<Node, kReservedBits, kTabSizeLog>::args_type |
152 | StackDepotBase<Node, kReservedBits, kTabSizeLog>::Get(u32 id) { |
153 | if (id == 0) |
154 | return args_type(); |
155 | CHECK_EQ(id & (((u32)-1) >> kReservedBits), id); |
156 | if (!nodes.contains(id)) |
157 | return args_type(); |
158 | const Node &node = nodes[id]; |
159 | return node.load(id); |
160 | } |
161 | |
162 | template <class Node, int kReservedBits, int kTabSizeLog> |
163 | void StackDepotBase<Node, kReservedBits, kTabSizeLog>::LockBeforeFork() { |
164 | // Do not lock hash table. It's very expensive, but it's not rely needed. The |
165 | // parent process will neither lock nor unlock. Child process risks to be |
166 | // deadlocked on already locked buckets. To avoid deadlock we will unlock |
167 | // every locked buckets in `UnlockAfterFork`. This may affect consistency of |
168 | // the hash table, but the only issue is a few items inserted by parent |
169 | // process will be not found by child, and the child may insert them again, |
170 | // wasting some space in `stackStore`. |
171 | |
172 | // We still need to lock nodes. |
173 | nodes.Lock(); |
174 | } |
175 | |
176 | template <class Node, int kReservedBits, int kTabSizeLog> |
177 | void StackDepotBase<Node, kReservedBits, kTabSizeLog>::UnlockAfterFork( |
178 | bool fork_child) { |
179 | nodes.Unlock(); |
180 | |
181 | // Only unlock in child process to avoid deadlock. See `LockBeforeFork`. |
182 | if (!fork_child) |
183 | return; |
184 | |
185 | for (int i = 0; i < kTabSize; ++i) { |
186 | atomic_uint32_t *p = &tab[i]; |
187 | uptr s = atomic_load(a: p, mo: memory_order_relaxed); |
188 | if (s & kLockMask) |
189 | unlock(p, s: s & kUnlockMask); |
190 | } |
191 | } |
192 | |
193 | template <class Node, int kReservedBits, int kTabSizeLog> |
194 | void StackDepotBase<Node, kReservedBits, kTabSizeLog>::PrintAll() { |
195 | for (int i = 0; i < kTabSize; ++i) { |
196 | atomic_uint32_t *p = &tab[i]; |
197 | u32 s = atomic_load(a: p, mo: memory_order_consume) & kUnlockMask; |
198 | for (; s;) { |
199 | const Node &node = nodes[s]; |
200 | Printf(format: "Stack for id %u:\n" , s); |
201 | node.load(s).Print(); |
202 | s = node.link; |
203 | } |
204 | } |
205 | } |
206 | |
207 | } // namespace __sanitizer |
208 | |
209 | #endif // SANITIZER_STACKDEPOTBASE_H |
210 | |