/src/sentencepiece/third_party/protobuf-lite/arena.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Protocol Buffers - Google's data interchange format |
2 | | // Copyright 2008 Google Inc. All rights reserved. |
3 | | // https://developers.google.com/protocol-buffers/ |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without |
6 | | // modification, are permitted provided that the following conditions are |
7 | | // met: |
8 | | // |
9 | | // * Redistributions of source code must retain the above copyright |
10 | | // notice, this list of conditions and the following disclaimer. |
11 | | // * Redistributions in binary form must reproduce the above |
12 | | // copyright notice, this list of conditions and the following disclaimer |
13 | | // in the documentation and/or other materials provided with the |
14 | | // distribution. |
15 | | // * Neither the name of Google Inc. nor the names of its |
16 | | // contributors may be used to endorse or promote products derived from |
17 | | // this software without specific prior written permission. |
18 | | // |
19 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | | |
31 | | #include <google/protobuf/arena.h> |
32 | | |
33 | | #include <algorithm> |
34 | | #include <atomic> |
35 | | #include <limits> |
36 | | |
37 | | #include <google/protobuf/stubs/mutex.h> |
38 | | |
39 | | #ifdef ADDRESS_SANITIZER |
40 | | #include <sanitizer/asan_interface.h> |
41 | | #endif // ADDRESS_SANITIZER |
42 | | |
43 | | #include <google/protobuf/port_def.inc> |
44 | | |
45 | | static const size_t kMinCleanupListElements = 8; |
46 | | static const size_t kMaxCleanupListElements = 64; // 1kB on 64-bit. |
47 | | |
48 | | namespace google { |
49 | | namespace protobuf { |
50 | | |
51 | | PROTOBUF_EXPORT /*static*/ void* (*const ArenaOptions::kDefaultBlockAlloc)( |
52 | | size_t) = &::operator new; |
53 | | |
54 | | namespace internal { |
55 | | |
56 | | |
57 | | ArenaImpl::CacheAlignedLifecycleIdGenerator ArenaImpl::lifecycle_id_generator_; |
58 | | #if defined(GOOGLE_PROTOBUF_NO_THREADLOCAL) |
59 | | ArenaImpl::ThreadCache& ArenaImpl::thread_cache() { |
60 | | static internal::ThreadLocalStorage<ThreadCache>* thread_cache_ = |
61 | | new internal::ThreadLocalStorage<ThreadCache>(); |
62 | | return *thread_cache_->Get(); |
63 | | } |
64 | | #elif defined(PROTOBUF_USE_DLLS) |
65 | | ArenaImpl::ThreadCache& ArenaImpl::thread_cache() { |
66 | | static PROTOBUF_THREAD_LOCAL ThreadCache thread_cache_ = { |
67 | | 0, static_cast<LifecycleIdAtomic>(-1), nullptr}; |
68 | | return thread_cache_; |
69 | | } |
70 | | #else |
71 | | PROTOBUF_THREAD_LOCAL ArenaImpl::ThreadCache ArenaImpl::thread_cache_ = { |
72 | | 0, static_cast<LifecycleIdAtomic>(-1), nullptr}; |
73 | | #endif |
74 | | |
75 | 0 | void ArenaFree(void* object, size_t size) { |
76 | | #if defined(__GXX_DELETE_WITH_SIZE__) || defined(__cpp_sized_deallocation) |
77 | | ::operator delete(object, size); |
78 | | #else |
79 | 0 | (void)size; |
80 | 0 | ::operator delete(object); |
81 | 0 | #endif |
82 | 0 | } |
83 | | |
84 | 0 | ArenaImpl::ArenaImpl(const ArenaOptions& options) { |
85 | 0 | ArenaMetricsCollector* collector = nullptr; |
86 | 0 | bool record_allocs = false; |
87 | 0 | if (options.make_metrics_collector != nullptr) { |
88 | 0 | collector = (*options.make_metrics_collector)(); |
89 | 0 | record_allocs = (collector && collector->RecordAllocs()); |
90 | 0 | } |
91 | | |
92 | | // Get memory where we can store non-default options if needed. |
93 | | // Use supplied initial_block if it is large enough. |
94 | 0 | size_t min_block_size = kOptionsSize + kBlockHeaderSize + kSerialArenaSize; |
95 | 0 | char* mem = options.initial_block; |
96 | 0 | size_t mem_size = options.initial_block_size; |
97 | 0 | GOOGLE_DCHECK_EQ(reinterpret_cast<uintptr_t>(mem) & 7, 0); |
98 | 0 | if (mem == nullptr || mem_size < min_block_size) { |
99 | | // Supplied initial block is not big enough. |
100 | 0 | mem_size = std::max(min_block_size, options.start_block_size); |
101 | 0 | mem = reinterpret_cast<char*>((*options.block_alloc)(mem_size)); |
102 | 0 | } |
103 | | |
104 | | // Create the special block. |
105 | 0 | const bool special = true; |
106 | 0 | const bool user_owned = (mem == options.initial_block); |
107 | 0 | auto block = |
108 | 0 | new (mem) SerialArena::Block(mem_size, nullptr, special, user_owned); |
109 | | |
110 | | // Options occupy the beginning of the initial block. |
111 | 0 | options_ = new (block->Pointer(block->pos())) Options; |
112 | | #ifdef ADDRESS_SANITIZER |
113 | | ASAN_UNPOISON_MEMORY_REGION(options_, kOptionsSize); |
114 | | #endif // ADDRESS_SANITIZER |
115 | 0 | options_->start_block_size = options.start_block_size; |
116 | 0 | options_->max_block_size = options.max_block_size; |
117 | 0 | options_->block_alloc = options.block_alloc; |
118 | 0 | options_->block_dealloc = options.block_dealloc; |
119 | 0 | options_->metrics_collector = collector; |
120 | 0 | block->set_pos(block->pos() + kOptionsSize); |
121 | |
|
122 | 0 | Init(record_allocs); |
123 | 0 | SetInitialBlock(block); |
124 | 0 | } |
125 | | |
126 | 0 | void ArenaImpl::Init(bool record_allocs) { |
127 | 0 | ThreadCache& tc = thread_cache(); |
128 | 0 | auto id = tc.next_lifecycle_id; |
129 | 0 | constexpr uint64 kInc = ThreadCache::kPerThreadIds * 2; |
130 | 0 | if (PROTOBUF_PREDICT_FALSE((id & (kInc - 1)) == 0)) { |
131 | 0 | if (sizeof(lifecycle_id_generator_.id) == 4) { |
132 | | // 2^32 is dangerous low to guarantee uniqueness. If we start dolling out |
133 | | // unique id's in ranges of kInc it's unacceptably low. In this case |
134 | | // we increment by 1. The additional range of kPerThreadIds that are used |
135 | | // per thread effectively pushes the overflow time from weeks to years |
136 | | // of continuous running. |
137 | 0 | id = lifecycle_id_generator_.id.fetch_add(1, std::memory_order_relaxed) * |
138 | 0 | kInc; |
139 | 0 | } else { |
140 | 0 | id = |
141 | 0 | lifecycle_id_generator_.id.fetch_add(kInc, std::memory_order_relaxed); |
142 | 0 | } |
143 | 0 | } |
144 | 0 | tc.next_lifecycle_id = id + 2; |
145 | | // We store "record_allocs" in the low bit of lifecycle_id_. |
146 | 0 | lifecycle_id_ = id | (record_allocs ? 1 : 0); |
147 | 0 | hint_.store(nullptr, std::memory_order_relaxed); |
148 | 0 | threads_.store(nullptr, std::memory_order_relaxed); |
149 | 0 | space_allocated_.store(0, std::memory_order_relaxed); |
150 | 0 | } |
151 | | |
152 | 0 | void ArenaImpl::SetInitialBlock(SerialArena::Block* block) { |
153 | | // Calling thread owns the first block. This allows the single-threaded case |
154 | | // to allocate on the first block without having to perform atomic operations. |
155 | 0 | SerialArena* serial = SerialArena::New(block, &thread_cache(), this); |
156 | 0 | serial->set_next(NULL); |
157 | 0 | threads_.store(serial, std::memory_order_relaxed); |
158 | 0 | space_allocated_.store(block->size(), std::memory_order_relaxed); |
159 | 0 | CacheSerialArena(serial); |
160 | 0 | } |
161 | | |
162 | 0 | ArenaImpl::~ArenaImpl() { |
163 | | // Have to do this in a first pass, because some of the destructors might |
164 | | // refer to memory in other blocks. |
165 | 0 | CleanupList(); |
166 | |
|
167 | 0 | ArenaMetricsCollector* collector = nullptr; |
168 | 0 | auto deallocator = &ArenaFree; |
169 | 0 | if (options_) { |
170 | 0 | collector = options_->metrics_collector; |
171 | 0 | deallocator = options_->block_dealloc; |
172 | 0 | } |
173 | |
|
174 | 0 | PerBlock([deallocator](SerialArena::Block* b) { |
175 | | #ifdef ADDRESS_SANITIZER |
176 | | // This memory was provided by the underlying allocator as unpoisoned, so |
177 | | // return it in an unpoisoned state. |
178 | | ASAN_UNPOISON_MEMORY_REGION(b->Pointer(0), b->size()); |
179 | | #endif // ADDRESS_SANITIZER |
180 | 0 | if (!b->user_owned()) { |
181 | 0 | (*deallocator)(b, b->size()); |
182 | 0 | } |
183 | 0 | }); |
184 | |
|
185 | 0 | if (collector) { |
186 | 0 | collector->OnDestroy(SpaceAllocated()); |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | 0 | uint64 ArenaImpl::Reset() { |
191 | 0 | if (options_ && options_->metrics_collector) { |
192 | 0 | options_->metrics_collector->OnReset(SpaceAllocated()); |
193 | 0 | } |
194 | | |
195 | | // Have to do this in a first pass, because some of the destructors might |
196 | | // refer to memory in other blocks. |
197 | 0 | CleanupList(); |
198 | | |
199 | | // Discard all blocks except the special block (if present). |
200 | 0 | uint64 space_allocated = 0; |
201 | 0 | SerialArena::Block* special_block = nullptr; |
202 | 0 | auto deallocator = (options_ ? options_->block_dealloc : &ArenaFree); |
203 | 0 | PerBlock( |
204 | 0 | [&space_allocated, &special_block, deallocator](SerialArena::Block* b) { |
205 | 0 | space_allocated += b->size(); |
206 | | #ifdef ADDRESS_SANITIZER |
207 | | // This memory was provided by the underlying allocator as unpoisoned, |
208 | | // so return it in an unpoisoned state. |
209 | | ASAN_UNPOISON_MEMORY_REGION(b->Pointer(0), b->size()); |
210 | | #endif // ADDRESS_SANITIZER |
211 | 0 | if (!b->special()) { |
212 | 0 | (*deallocator)(b, b->size()); |
213 | 0 | } else { |
214 | | // Prepare special block for reuse. |
215 | | // Note: if options_ is present, it occupies the beginning of the |
216 | | // block and therefore pos is advanced past it. |
217 | 0 | GOOGLE_DCHECK(special_block == nullptr); |
218 | 0 | special_block = b; |
219 | 0 | } |
220 | 0 | }); |
221 | |
|
222 | 0 | Init(record_allocs()); |
223 | 0 | if (special_block != nullptr) { |
224 | | // next() should still be nullptr since we are using a stack discipline, but |
225 | | // clear it anyway to reduce fragility. |
226 | 0 | GOOGLE_DCHECK_EQ(special_block->next(), nullptr); |
227 | 0 | special_block->clear_next(); |
228 | 0 | special_block->set_pos(kBlockHeaderSize + (options_ ? kOptionsSize : 0)); |
229 | 0 | SetInitialBlock(special_block); |
230 | 0 | } |
231 | 0 | return space_allocated; |
232 | 0 | } |
233 | | |
234 | | std::pair<void*, size_t> ArenaImpl::NewBuffer(size_t last_size, |
235 | 0 | size_t min_bytes) { |
236 | 0 | size_t size; |
237 | 0 | if (last_size != -1) { |
238 | | // Double the current block size, up to a limit. |
239 | 0 | auto max_size = options_ ? options_->max_block_size : kDefaultMaxBlockSize; |
240 | 0 | size = std::min(2 * last_size, max_size); |
241 | 0 | } else { |
242 | 0 | size = options_ ? options_->start_block_size : kDefaultStartBlockSize; |
243 | 0 | } |
244 | | // Verify that min_bytes + kBlockHeaderSize won't overflow. |
245 | 0 | GOOGLE_CHECK_LE(min_bytes, std::numeric_limits<size_t>::max() - kBlockHeaderSize); |
246 | 0 | size = std::max(size, kBlockHeaderSize + min_bytes); |
247 | |
|
248 | 0 | void* mem = options_ ? (*options_->block_alloc)(size) : ::operator new(size); |
249 | 0 | space_allocated_.fetch_add(size, std::memory_order_relaxed); |
250 | 0 | return {mem, size}; |
251 | 0 | } |
252 | | |
253 | | SerialArena::Block* SerialArena::NewBlock(SerialArena::Block* last_block, |
254 | 0 | size_t min_bytes, ArenaImpl* arena) { |
255 | 0 | void* mem; |
256 | 0 | size_t size; |
257 | 0 | std::tie(mem, size) = |
258 | 0 | arena->NewBuffer(last_block ? last_block->size() : -1, min_bytes); |
259 | 0 | Block* b = new (mem) Block(size, last_block, false, false); |
260 | 0 | return b; |
261 | 0 | } |
262 | | |
263 | | PROTOBUF_NOINLINE |
264 | 0 | void SerialArena::AddCleanupFallback(void* elem, void (*cleanup)(void*)) { |
265 | 0 | size_t size = cleanup_ ? cleanup_->size * 2 : kMinCleanupListElements; |
266 | 0 | size = std::min(size, kMaxCleanupListElements); |
267 | 0 | size_t bytes = internal::AlignUpTo8(CleanupChunk::SizeOf(size)); |
268 | 0 | CleanupChunk* list = reinterpret_cast<CleanupChunk*>(AllocateAligned(bytes)); |
269 | 0 | list->next = cleanup_; |
270 | 0 | list->size = size; |
271 | |
|
272 | 0 | cleanup_ = list; |
273 | 0 | cleanup_ptr_ = &list->nodes[0]; |
274 | 0 | cleanup_limit_ = &list->nodes[size]; |
275 | |
|
276 | 0 | AddCleanup(elem, cleanup); |
277 | 0 | } |
278 | | |
279 | | void* ArenaImpl::AllocateAlignedAndAddCleanup(size_t n, |
280 | 0 | void (*cleanup)(void*)) { |
281 | 0 | SerialArena* arena; |
282 | 0 | if (PROTOBUF_PREDICT_TRUE(GetSerialArenaFast(&arena))) { |
283 | 0 | return arena->AllocateAlignedAndAddCleanup(n, cleanup); |
284 | 0 | } else { |
285 | 0 | return AllocateAlignedAndAddCleanupFallback(n, cleanup); |
286 | 0 | } |
287 | 0 | } |
288 | | |
289 | 0 | void ArenaImpl::AddCleanup(void* elem, void (*cleanup)(void*)) { |
290 | 0 | SerialArena* arena; |
291 | 0 | if (PROTOBUF_PREDICT_TRUE(GetSerialArenaFast(&arena))) { |
292 | 0 | arena->AddCleanup(elem, cleanup); |
293 | 0 | } else { |
294 | 0 | return AddCleanupFallback(elem, cleanup); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | PROTOBUF_NOINLINE |
299 | 0 | void* ArenaImpl::AllocateAlignedFallback(size_t n) { |
300 | 0 | return GetSerialArenaFallback(&thread_cache())->AllocateAligned(n); |
301 | 0 | } |
302 | | |
303 | | PROTOBUF_NOINLINE |
304 | | void* ArenaImpl::AllocateAlignedAndAddCleanupFallback(size_t n, |
305 | 0 | void (*cleanup)(void*)) { |
306 | 0 | return GetSerialArenaFallback( |
307 | 0 | &thread_cache())->AllocateAlignedAndAddCleanup(n, cleanup); |
308 | 0 | } |
309 | | |
310 | | PROTOBUF_NOINLINE |
311 | 0 | void ArenaImpl::AddCleanupFallback(void* elem, void (*cleanup)(void*)) { |
312 | 0 | GetSerialArenaFallback(&thread_cache())->AddCleanup(elem, cleanup); |
313 | 0 | } |
314 | | |
315 | | PROTOBUF_NOINLINE |
316 | 0 | void* SerialArena::AllocateAlignedFallback(size_t n) { |
317 | | // Sync back to current's pos. |
318 | 0 | head_->set_pos(head_->size() - (limit_ - ptr_)); |
319 | |
|
320 | 0 | head_ = NewBlock(head_, n, arena_); |
321 | 0 | ptr_ = head_->Pointer(head_->pos()); |
322 | 0 | limit_ = head_->Pointer(head_->size()); |
323 | |
|
324 | | #ifdef ADDRESS_SANITIZER |
325 | | ASAN_POISON_MEMORY_REGION(ptr_, limit_ - ptr_); |
326 | | #endif // ADDRESS_SANITIZER |
327 | |
|
328 | 0 | return AllocateAligned(n); |
329 | 0 | } |
330 | | |
331 | 0 | uint64 ArenaImpl::SpaceAllocated() const { |
332 | 0 | return space_allocated_.load(std::memory_order_relaxed); |
333 | 0 | } |
334 | | |
335 | 0 | uint64 ArenaImpl::SpaceUsed() const { |
336 | 0 | SerialArena* serial = threads_.load(std::memory_order_acquire); |
337 | 0 | uint64 space_used = 0; |
338 | 0 | for (; serial; serial = serial->next()) { |
339 | 0 | space_used += serial->SpaceUsed(); |
340 | 0 | } |
341 | | // Remove the overhead of Options structure, if any. |
342 | 0 | if (options_) { |
343 | 0 | space_used -= kOptionsSize; |
344 | 0 | } |
345 | 0 | return space_used; |
346 | 0 | } |
347 | | |
348 | 0 | uint64 SerialArena::SpaceUsed() const { |
349 | | // Get current block's size from ptr_ (since we can't trust head_->pos(). |
350 | 0 | uint64 space_used = ptr_ - head_->Pointer(kBlockHeaderSize); |
351 | | // Get subsequent block size from b->pos(). |
352 | 0 | for (Block* b = head_->next(); b; b = b->next()) { |
353 | 0 | space_used += (b->pos() - kBlockHeaderSize); |
354 | 0 | } |
355 | | // Remove the overhead of the SerialArena itself. |
356 | 0 | space_used -= ArenaImpl::kSerialArenaSize; |
357 | 0 | return space_used; |
358 | 0 | } |
359 | | |
360 | 0 | void ArenaImpl::CleanupList() { |
361 | | // By omitting an Acquire barrier we ensure that any user code that doesn't |
362 | | // properly synchronize Reset() or the destructor will throw a TSAN warning. |
363 | 0 | SerialArena* serial = threads_.load(std::memory_order_relaxed); |
364 | |
|
365 | 0 | for (; serial; serial = serial->next()) { |
366 | 0 | serial->CleanupList(); |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | 0 | void SerialArena::CleanupList() { |
371 | 0 | if (cleanup_ != NULL) { |
372 | 0 | CleanupListFallback(); |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | 0 | void SerialArena::CleanupListFallback() { |
377 | | // The first chunk might be only partially full, so calculate its size |
378 | | // from cleanup_ptr_. Subsequent chunks are always full, so use list->size. |
379 | 0 | size_t n = cleanup_ptr_ - &cleanup_->nodes[0]; |
380 | 0 | CleanupChunk* list = cleanup_; |
381 | 0 | while (true) { |
382 | 0 | CleanupNode* node = &list->nodes[0]; |
383 | | // Cleanup newest elements first (allocated last). |
384 | 0 | for (size_t i = n; i > 0; i--) { |
385 | 0 | node[i - 1].cleanup(node[i - 1].elem); |
386 | 0 | } |
387 | 0 | list = list->next; |
388 | 0 | if (list == nullptr) { |
389 | 0 | break; |
390 | 0 | } |
391 | | // All but the first chunk are always full. |
392 | 0 | n = list->size; |
393 | 0 | } |
394 | 0 | } |
395 | | |
396 | 0 | SerialArena* SerialArena::New(Block* b, void* owner, ArenaImpl* arena) { |
397 | 0 | auto pos = b->pos(); |
398 | 0 | GOOGLE_DCHECK_LE(pos + ArenaImpl::kSerialArenaSize, b->size()); |
399 | 0 | SerialArena* serial = reinterpret_cast<SerialArena*>(b->Pointer(pos)); |
400 | 0 | b->set_pos(pos + ArenaImpl::kSerialArenaSize); |
401 | 0 | serial->arena_ = arena; |
402 | 0 | serial->owner_ = owner; |
403 | 0 | serial->head_ = b; |
404 | 0 | serial->ptr_ = b->Pointer(b->pos()); |
405 | 0 | serial->limit_ = b->Pointer(b->size()); |
406 | 0 | serial->cleanup_ = NULL; |
407 | 0 | serial->cleanup_ptr_ = NULL; |
408 | 0 | serial->cleanup_limit_ = NULL; |
409 | 0 | return serial; |
410 | 0 | } |
411 | | |
412 | | PROTOBUF_NOINLINE |
413 | 0 | SerialArena* ArenaImpl::GetSerialArenaFallback(void* me) { |
414 | | // Look for this SerialArena in our linked list. |
415 | 0 | SerialArena* serial = threads_.load(std::memory_order_acquire); |
416 | 0 | for (; serial; serial = serial->next()) { |
417 | 0 | if (serial->owner() == me) { |
418 | 0 | break; |
419 | 0 | } |
420 | 0 | } |
421 | |
|
422 | 0 | if (!serial) { |
423 | | // This thread doesn't have any SerialArena, which also means it doesn't |
424 | | // have any blocks yet. So we'll allocate its first block now. |
425 | 0 | SerialArena::Block* b = SerialArena::NewBlock(NULL, kSerialArenaSize, this); |
426 | 0 | serial = SerialArena::New(b, me, this); |
427 | |
|
428 | 0 | SerialArena* head = threads_.load(std::memory_order_relaxed); |
429 | 0 | do { |
430 | 0 | serial->set_next(head); |
431 | 0 | } while (!threads_.compare_exchange_weak( |
432 | 0 | head, serial, std::memory_order_release, std::memory_order_relaxed)); |
433 | 0 | } |
434 | |
|
435 | 0 | CacheSerialArena(serial); |
436 | 0 | return serial; |
437 | 0 | } |
438 | | |
439 | 0 | ArenaMetricsCollector::~ArenaMetricsCollector() {} |
440 | | |
441 | | } // namespace internal |
442 | | |
443 | | PROTOBUF_FUNC_ALIGN(32) |
444 | 0 | void* Arena::AllocateAlignedNoHook(size_t n) { |
445 | 0 | return impl_.AllocateAligned(n); |
446 | 0 | } |
447 | | |
448 | | } // namespace protobuf |
449 | | } // namespace google |