Coverage Report

Created: 2023-06-07 07:09

/src/LPM/external.protobuf/include/google/protobuf/serial_arena.h
Line
Count
Source (jump to first uncovered line)
1
// Protocol Buffers - Google's data interchange format
2
// Copyright 2022 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
// This file defines the internal class SerialArena
32
33
#ifndef GOOGLE_PROTOBUF_SERIAL_ARENA_H__
34
#define GOOGLE_PROTOBUF_SERIAL_ARENA_H__
35
36
#include <algorithm>
37
#include <atomic>
38
#include <string>
39
#include <type_traits>
40
#include <typeinfo>
41
#include <utility>
42
43
#include "google/protobuf/stubs/common.h"
44
#include "absl/base/attributes.h"
45
#include "absl/log/absl_check.h"
46
#include "absl/numeric/bits.h"
47
#include "google/protobuf/arena_align.h"
48
#include "google/protobuf/arena_cleanup.h"
49
#include "google/protobuf/arena_config.h"
50
#include "google/protobuf/arenaz_sampler.h"
51
#include "google/protobuf/port.h"
52
#include "google/protobuf/string_block.h"
53
54
55
// Must be included last.
56
#include "google/protobuf/port_def.inc"
57
58
namespace google {
59
namespace protobuf {
60
namespace internal {
61
62
// Arena blocks are variable length malloc-ed objects.  The following structure
63
// describes the common header for all blocks.
64
struct ArenaBlock {
65
  // For the sentry block with zero-size where ptr_, limit_, cleanup_nodes all
66
  // point to "this".
67
  constexpr ArenaBlock()
68
0
      : next(nullptr), cleanup_nodes(this), size(0) {}
69
70
  ArenaBlock(ArenaBlock* next, size_t size)
71
0
      : next(next), cleanup_nodes(nullptr), size(size) {
72
0
    ABSL_DCHECK_GT(size, sizeof(ArenaBlock));
73
0
  }
74
75
0
  char* Pointer(size_t n) {
76
0
    ABSL_DCHECK_LE(n, size);
77
0
    return reinterpret_cast<char*>(this) + n;
78
0
  }
79
0
  char* Limit() { return Pointer(size & static_cast<size_t>(-8)); }
80
81
0
  bool IsSentry() const { return size == 0; }
82
83
  ArenaBlock* const next;
84
  void* cleanup_nodes;
85
  const size_t size;
86
  // data follows
87
};
88
89
enum class AllocationClient { kDefault, kArray };
90
91
class ThreadSafeArena;
92
93
// Tag type used to invoke the constructor of the first SerialArena.
94
struct FirstSerialArena {
95
  explicit FirstSerialArena() = default;
96
};
97
98
// A simple arena allocator. Calls to allocate functions must be properly
99
// serialized by the caller, hence this class cannot be used as a general
100
// purpose allocator in a multi-threaded program. It serves as a building block
101
// for ThreadSafeArena, which provides a thread-safe arena allocator.
102
//
103
// This class manages
104
// 1) Arena bump allocation + owning memory blocks.
105
// 2) Maintaining a cleanup list.
106
// It delegates the actual memory allocation back to ThreadSafeArena, which
107
// contains the information on block growth policy and backing memory allocation
108
// used.
109
class PROTOBUF_EXPORT SerialArena {
110
 public:
111
  void CleanupList();
112
0
  size_t FreeStringBlocks() {
113
0
    // On the active block delete all strings skipping the unused instances.
114
0
    size_t unused_bytes = string_block_unused_.load(std::memory_order_relaxed);
115
0
    if (string_block_ != nullptr) {
116
0
      return FreeStringBlocks(string_block_, unused_bytes);
117
0
    }
118
0
    return 0;
119
0
  }
120
0
  uint64_t SpaceAllocated() const {
121
0
    return space_allocated_.load(std::memory_order_relaxed);
122
0
  }
123
  uint64_t SpaceUsed() const;
124
125
0
  bool HasSpace(size_t n) const {
126
0
    return n <= static_cast<size_t>(limit_ - ptr());
127
0
  }
128
129
  // See comments on `cached_blocks_` member for details.
130
0
  PROTOBUF_ALWAYS_INLINE void* TryAllocateFromCachedBlock(size_t size) {
131
0
    if (PROTOBUF_PREDICT_FALSE(size < 16)) return nullptr;
132
0
    // We round up to the next larger block in case the memory doesn't match
133
0
    // the pattern we are looking for.
134
0
    const size_t index = absl::bit_width(size - 1) - 4;
135
0
136
0
    if (index >= cached_block_length_) return nullptr;
137
0
    auto& cached_head = cached_blocks_[index];
138
0
    if (cached_head == nullptr) return nullptr;
139
0
140
0
    void* ret = cached_head;
141
0
    PROTOBUF_UNPOISON_MEMORY_REGION(ret, size);
142
0
    cached_head = cached_head->next;
143
0
    return ret;
144
0
  }
145
146
  // In kArray mode we look through cached blocks.
147
  // We do not do this by default because most non-array allocations will not
148
  // have the right size and will fail to find an appropriate cached block.
149
  //
150
  // TODO(sbenza): Evaluate if we should use cached blocks for message types of
151
  // the right size. We can statically know if the allocation size can benefit
152
  // from it.
153
  template <AllocationClient alloc_client = AllocationClient::kDefault>
154
  void* AllocateAligned(size_t n) {
155
    ABSL_DCHECK(internal::ArenaAlignDefault::IsAligned(n));
156
    ABSL_DCHECK_GE(limit_, ptr());
157
158
    if (alloc_client == AllocationClient::kArray) {
159
      if (void* res = TryAllocateFromCachedBlock(n)) {
160
        return res;
161
      }
162
    }
163
164
    if (PROTOBUF_PREDICT_FALSE(!HasSpace(n))) {
165
      return AllocateAlignedFallback(n);
166
    }
167
    return AllocateFromExisting(n);
168
  }
169
170
 private:
171
  static inline PROTOBUF_ALWAYS_INLINE constexpr size_t AlignUpTo(size_t n,
172
0
                                                                  size_t a) {
173
0
    // We are wasting space by over allocating align - 8 bytes. Compared to a
174
0
    // dedicated function that takes current alignment in consideration.  Such a
175
0
    // scheme would only waste (align - 8)/2 bytes on average, but requires a
176
0
    // dedicated function in the outline arena allocation functions. Possibly
177
0
    // re-evaluate tradeoffs later.
178
0
    return a <= 8 ? ArenaAlignDefault::Ceil(n) : ArenaAlignAs(a).Padded(n);
179
0
  }
180
181
0
  static inline PROTOBUF_ALWAYS_INLINE void* AlignTo(void* p, size_t a) {
182
0
    return (a <= ArenaAlignDefault::align)
183
0
               ? ArenaAlignDefault::CeilDefaultAligned(p)
184
0
               : ArenaAlignAs(a).CeilDefaultAligned(p);
185
0
  }
186
187
0
  void* AllocateFromExisting(size_t n) {
188
0
    PROTOBUF_UNPOISON_MEMORY_REGION(ptr(), n);
189
0
    void* ret = ptr();
190
0
    set_ptr(static_cast<char*>(ret) + n);
191
0
    return ret;
192
0
  }
193
194
  // See comments on `cached_blocks_` member for details.
195
0
  void ReturnArrayMemory(void* p, size_t size) {
196
0
    // We only need to check for 32-bit platforms.
197
0
    // In 64-bit platforms the minimum allocation size from Repeated*Field will
198
0
    // be 16 guaranteed.
199
0
    if (sizeof(void*) < 8) {
200
0
      if (PROTOBUF_PREDICT_FALSE(size < 16)) return;
201
0
    } else {
202
0
      PROTOBUF_ASSUME(size >= 16);
203
0
    }
204
0
205
0
    // We round down to the next smaller block in case the memory doesn't match
206
0
    // the pattern we are looking for. eg, someone might have called Reserve()
207
0
    // on the repeated field.
208
0
    const size_t index = absl::bit_width(size) - 5;
209
0
210
0
    if (PROTOBUF_PREDICT_FALSE(index >= cached_block_length_)) {
211
0
      // We can't put this object on the freelist so make this object the
212
0
      // freelist. It is guaranteed it is larger than the one we have, and
213
0
      // large enough to hold another allocation of `size`.
214
0
      CachedBlock** new_list = static_cast<CachedBlock**>(p);
215
0
      size_t new_size = size / sizeof(CachedBlock*);
216
0
217
0
      std::copy(cached_blocks_, cached_blocks_ + cached_block_length_,
218
0
                new_list);
219
0
220
0
      // We need to unpoison this memory before filling it in case it has been
221
0
      // poisoned by another santizer client.
222
0
      PROTOBUF_UNPOISON_MEMORY_REGION(
223
0
          new_list + cached_block_length_,
224
0
          (new_size - cached_block_length_) * sizeof(CachedBlock*));
225
0
226
0
      std::fill(new_list + cached_block_length_, new_list + new_size, nullptr);
227
0
228
0
      cached_blocks_ = new_list;
229
0
      // Make the size fit in uint8_t. This is the power of two, so we don't
230
0
      // need anything larger.
231
0
      cached_block_length_ =
232
0
          static_cast<uint8_t>(std::min(size_t{64}, new_size));
233
0
234
0
      return;
235
0
    }
236
0
237
0
    auto& cached_head = cached_blocks_[index];
238
0
    auto* new_node = static_cast<CachedBlock*>(p);
239
0
    new_node->next = cached_head;
240
0
    cached_head = new_node;
241
0
    PROTOBUF_POISON_MEMORY_REGION(p, size);
242
0
  }
243
244
 public:
245
  // Allocate space if the current region provides enough space.
246
0
  bool MaybeAllocateAligned(size_t n, void** out) {
247
0
    ABSL_DCHECK(internal::ArenaAlignDefault::IsAligned(n));
248
0
    ABSL_DCHECK_GE(limit_, ptr());
249
0
    if (PROTOBUF_PREDICT_FALSE(!HasSpace(n))) return false;
250
0
    *out = AllocateFromExisting(n);
251
0
    return true;
252
0
  }
253
254
  // If there is enough space in the current block, allocate space for one `T`
255
  // object and register for destruction. The object has not been constructed
256
  // and the memory returned is uninitialized.
257
  template <typename T>
258
  PROTOBUF_ALWAYS_INLINE void* MaybeAllocateWithCleanup() {
259
    ABSL_DCHECK_GE(limit_, ptr());
260
    static_assert(!std::is_trivially_destructible<T>::value,
261
                  "This function is only for non-trivial types.");
262
263
    constexpr int aligned_size = ArenaAlignDefault::Ceil(sizeof(T));
264
    constexpr auto destructor = cleanup::arena_destruct_object<T>;
265
    size_t required = aligned_size + cleanup::Size(destructor);
266
    if (PROTOBUF_PREDICT_FALSE(!HasSpace(required))) {
267
      return nullptr;
268
    }
269
    void* ptr = AllocateFromExistingWithCleanupFallback(aligned_size,
270
                                                        alignof(T), destructor);
271
    PROTOBUF_ASSUME(ptr != nullptr);
272
    return ptr;
273
  }
274
275
  PROTOBUF_ALWAYS_INLINE
276
  void* AllocateAlignedWithCleanup(size_t n, size_t align,
277
0
                                   void (*destructor)(void*)) {
278
0
    size_t required = AlignUpTo(n, align) + cleanup::Size(destructor);
279
0
    if (PROTOBUF_PREDICT_FALSE(!HasSpace(required))) {
280
0
      return AllocateAlignedWithCleanupFallback(n, align, destructor);
281
0
    }
282
0
    return AllocateFromExistingWithCleanupFallback(n, align, destructor);
283
0
  }
284
285
  PROTOBUF_ALWAYS_INLINE
286
0
  void AddCleanup(void* elem, void (*destructor)(void*)) {
287
0
    size_t required = cleanup::Size(destructor);
288
0
    if (PROTOBUF_PREDICT_FALSE(!HasSpace(required))) {
289
0
      return AddCleanupFallback(elem, destructor);
290
0
    }
291
0
    AddCleanupFromExisting(elem, destructor);
292
0
  }
293
294
  ABSL_ATTRIBUTE_RETURNS_NONNULL void* AllocateFromStringBlock();
295
296
 private:
297
  bool MaybeAllocateString(void*& p);
298
  ABSL_ATTRIBUTE_RETURNS_NONNULL void* AllocateFromStringBlockFallback();
299
300
  void* AllocateFromExistingWithCleanupFallback(size_t n, size_t align,
301
0
                                                void (*destructor)(void*)) {
302
0
    n = AlignUpTo(n, align);
303
0
    PROTOBUF_UNPOISON_MEMORY_REGION(ptr(), n);
304
0
    void* ret = ArenaAlignAs(align).CeilDefaultAligned(ptr());
305
0
    set_ptr(ptr() + n);
306
0
    ABSL_DCHECK_GE(limit_, ptr());
307
0
    AddCleanupFromExisting(ret, destructor);
308
0
    return ret;
309
0
  }
310
311
  PROTOBUF_ALWAYS_INLINE
312
0
  void AddCleanupFromExisting(void* elem, void (*destructor)(void*)) {
313
0
    cleanup::Tag tag = cleanup::Type(destructor);
314
0
    size_t n = cleanup::Size(tag);
315
0
316
0
    PROTOBUF_UNPOISON_MEMORY_REGION(limit_ - n, n);
317
0
    limit_ -= n;
318
0
    ABSL_DCHECK_GE(limit_, ptr());
319
0
    cleanup::CreateNode(tag, limit_, elem, destructor);
320
0
  }
321
322
 private:
323
  friend class ThreadSafeArena;
324
325
  // Creates a new SerialArena inside mem using the remaining memory as for
326
  // future allocations.
327
  // The `parent` arena must outlive the serial arena, which is guaranteed
328
  // because the parent manages the lifetime of the serial arenas.
329
  static SerialArena* New(SizedPtr mem, ThreadSafeArena& parent);
330
  // Free SerialArena returning the memory passed in to New
331
  template <typename Deallocator>
332
  SizedPtr Free(Deallocator deallocator);
333
334
  static size_t FreeStringBlocks(StringBlock* string_block, size_t unused);
335
336
  // Members are declared here to track sizeof(SerialArena) and hotness
337
  // centrally. They are (roughly) laid out in descending order of hotness.
338
339
  // Next pointer to allocate from.  Always 8-byte aligned.  Points inside
340
  // head_ (and head_->pos will always be non-canonical).  We keep these
341
  // here to reduce indirection.
342
  std::atomic<char*> ptr_{nullptr};
343
  // Limiting address up to which memory can be allocated from the head block.
344
  char* limit_ = nullptr;
345
346
  // The active string block.
347
  StringBlock* string_block_ = nullptr;
348
349
  // The number of unused bytes in string_block_.
350
  // We allocate from `effective_size()` down to 0 inside `string_block_`.
351
  // `unused  == 0` means that `string_block_` is exhausted. (or null).
352
  std::atomic<size_t> string_block_unused_{0};
353
354
  std::atomic<ArenaBlock*> head_{nullptr};  // Head of linked list of blocks.
355
  std::atomic<size_t> space_used_{0};       // Necessary for metrics.
356
  std::atomic<size_t> space_allocated_{0};
357
  ThreadSafeArena& parent_;
358
359
  // Repeated*Field and Arena play together to reduce memory consumption by
360
  // reusing blocks. Currently, natural growth of the repeated field types makes
361
  // them allocate blocks of size `8 + 2^N, N>=3`.
362
  // When the repeated field grows returns the previous block and we put it in
363
  // this free list.
364
  // `cached_blocks_[i]` points to the free list for blocks of size `8+2^(i+3)`.
365
  // The array of freelists is grown when needed in `ReturnArrayMemory()`.
366
  struct CachedBlock {
367
    // Simple linked list.
368
    CachedBlock* next;
369
  };
370
  uint8_t cached_block_length_ = 0;
371
  CachedBlock** cached_blocks_ = nullptr;
372
373
  // Helper getters/setters to handle relaxed operations on atomic variables.
374
0
  ArenaBlock* head() { return head_.load(std::memory_order_relaxed); }
375
0
  const ArenaBlock* head() const {
376
0
    return head_.load(std::memory_order_relaxed);
377
0
  }
378
379
0
  char* ptr() { return ptr_.load(std::memory_order_relaxed); }
380
0
  const char* ptr() const { return ptr_.load(std::memory_order_relaxed); }
381
0
  void set_ptr(char* ptr) { return ptr_.store(ptr, std::memory_order_relaxed); }
382
383
  // Constructor is private as only New() should be used.
384
  inline SerialArena(ArenaBlock* b, ThreadSafeArena& parent);
385
386
  // Constructors to handle the first SerialArena.
387
  inline explicit SerialArena(ThreadSafeArena& parent);
388
  inline SerialArena(FirstSerialArena, ArenaBlock* b, ThreadSafeArena& parent);
389
390
  void* AllocateAlignedFallback(size_t n);
391
  void* AllocateAlignedWithCleanupFallback(size_t n, size_t align,
392
                                           void (*destructor)(void*));
393
  void AddCleanupFallback(void* elem, void (*destructor)(void*));
394
  inline void AllocateNewBlock(size_t n);
395
  inline void Init(ArenaBlock* b, size_t offset);
396
397
 public:
398
  static constexpr size_t kBlockHeaderSize =
399
      ArenaAlignDefault::Ceil(sizeof(ArenaBlock));
400
};
401
402
0
inline PROTOBUF_ALWAYS_INLINE bool SerialArena::MaybeAllocateString(void*& p) {
403
0
  // Check how many unused instances are in the current block.
404
0
  size_t unused_bytes = string_block_unused_.load(std::memory_order_relaxed);
405
0
  if (PROTOBUF_PREDICT_TRUE(unused_bytes != 0)) {
406
0
    unused_bytes -= sizeof(std::string);
407
0
    string_block_unused_.store(unused_bytes, std::memory_order_relaxed);
408
0
    p = string_block_->AtOffset(unused_bytes);
409
0
    return true;
410
0
  }
411
0
  return false;
412
0
}
413
414
template <>
415
inline PROTOBUF_ALWAYS_INLINE void*
416
0
SerialArena::MaybeAllocateWithCleanup<std::string>() {
417
0
  void* p;
418
0
  return MaybeAllocateString(p) ? p : nullptr;
419
0
}
420
421
ABSL_ATTRIBUTE_RETURNS_NONNULL inline PROTOBUF_ALWAYS_INLINE void*
422
0
SerialArena::AllocateFromStringBlock() {
423
0
  void* p;
424
0
  if (ABSL_PREDICT_TRUE(MaybeAllocateString(p))) return p;
425
0
  return AllocateFromStringBlockFallback();
426
0
}
427
428
}  // namespace internal
429
}  // namespace protobuf
430
}  // namespace google
431
432
#include "google/protobuf/port_undef.inc"
433
434
#endif  // GOOGLE_PROTOBUF_SERIAL_ARENA_H__