blob: 2f6ff32c99b259898f4785cf34ab173c786956d1 [file] [log] [blame]
Brian Silvermanf51499a2020-09-21 12:49:08 -07001#ifndef AOS_CONTAINERS_RESIZEABLE_BUFFER_H_
2#define AOS_CONTAINERS_RESIZEABLE_BUFFER_H_
3
Tyler Chatowbf0609c2021-07-31 16:13:27 -07004#include <cstdlib>
Brian Silvermanf51499a2020-09-21 12:49:08 -07005#include <memory>
6
7#include "glog/logging.h"
8
9namespace aos {
10
11// Kind of like a subset of vector<uint8_t>, but with less destructor calls.
12// When building unoptimized, especially with sanitizers, the vector<uint8_t>
13// version ends up being really slow in tests.
Austin Schuhbed6eeb2023-02-04 11:42:03 -080014//
15// F is the allocator used for reallocating the memory used by the buffer.
16template <class F>
17class AllocatorResizeableBuffer {
Brian Silvermanf51499a2020-09-21 12:49:08 -070018 public:
Austin Schuhbed6eeb2023-02-04 11:42:03 -080019 AllocatorResizeableBuffer() = default;
Brian Silvermanf51499a2020-09-21 12:49:08 -070020
Philipp Schrader790cb542023-07-05 21:06:52 -070021 AllocatorResizeableBuffer(const AllocatorResizeableBuffer &other) {
22 *this = other;
23 }
24 AllocatorResizeableBuffer(AllocatorResizeableBuffer &&other) {
25 *this = std::move(other);
26 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080027 AllocatorResizeableBuffer &operator=(const AllocatorResizeableBuffer &other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070028 resize(other.size());
29 memcpy(storage_.get(), other.storage_.get(), size());
30 return *this;
31 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080032 AllocatorResizeableBuffer &operator=(AllocatorResizeableBuffer &&other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070033 std::swap(storage_, other.storage_);
34 std::swap(size_, other.size_);
35 std::swap(capacity_, other.capacity_);
36 return *this;
37 }
38
39 uint8_t *data() { return static_cast<uint8_t *>(storage_.get()); }
40 const uint8_t *data() const {
41 return static_cast<const uint8_t *>(storage_.get());
42 }
43
44 uint8_t *begin() { return data(); }
45 const uint8_t *begin() const { return data(); }
46 uint8_t *end() { return data() + size(); }
47 const uint8_t *end() const { return data() + size(); }
Brennan Coslett7a8533f2023-04-13 13:08:17 -050048 uint8_t &at(int index) { return *(data() + index); }
49 const uint8_t &at(int index) const { return *(data() + index); }
Brian Silvermanf51499a2020-09-21 12:49:08 -070050
51 size_t size() const { return size_; }
52 size_t capacity() const { return capacity_; }
53
Austin Schuh83548e92022-10-16 18:41:37 -070054 void reserve(size_t new_size) {
55 if (new_size > capacity_) {
56 Allocate(new_size);
57 }
58 }
59
Brian Silvermanf51499a2020-09-21 12:49:08 -070060 void resize(size_t new_size) {
61 if (new_size > capacity_) {
62 Allocate(new_size);
63 }
64 size_ = new_size;
65 }
66
67 void erase_front(size_t count) {
68 if (count == 0) {
69 return;
70 }
71 CHECK_LE(count, size_);
72 memmove(static_cast<void *>(data()), static_cast<void *>(data() + count),
73 size_ - count);
74 size_ -= count;
75 }
76
77 void push_back(uint8_t x) {
78 if (size_ == capacity_) {
79 Allocate(std::max<size_t>(16, size_ * 2));
80 }
81 *end() = x;
82 ++size_;
83 CHECK_LE(size_, capacity_);
84 }
85
86 private:
87 // We need this silly function because C++ is bad, and extensions to it which
88 // we work with make it a true nightmare.
89 //
90 // (a) You can't easily write out the signature of free because it depends on
91 // whether exceptions are enabled or not. You could use decltype, but see (b).
92 //
93 // (b) You can't easily write &free because CUDA overloads it with a
94 // __device__ version. You could cast to the appropriate version, but see (a).
95 //
96 // There's probably some kind of SFINAE thing which could find the matching
97 // signature from a set of choices, and then we could just
98 // static_cast<TheSignature>(&free). However, that sounds like a nightmare,
99 // especially because it has to conditionally enable the part mentioning CUDA
100 // identifiers in the preprocessor. This little function is way simpler.
101 static void DoFree(void *p) { free(p); }
102
103 void Allocate(size_t new_capacity) {
104 void *const old = storage_.release();
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800105 storage_.reset(CHECK_NOTNULL(F::Realloc(old, capacity_, new_capacity)));
Brian Silvermanf51499a2020-09-21 12:49:08 -0700106 capacity_ = new_capacity;
107 }
108
109 std::unique_ptr<void, decltype(&DoFree)> storage_{nullptr, &DoFree};
110 size_t size_ = 0, capacity_ = 0;
111};
112
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800113// An allocator which just uses realloc to allocate.
114class Reallocator {
115 public:
116 static void *Realloc(void *old, size_t /*old_size*/, size_t new_capacity) {
117 return realloc(old, new_capacity);
118 }
119};
120
121// A resizable buffer which uses realloc when it needs to grow to attempt to
122// avoid full coppies.
123class ResizeableBuffer : public AllocatorResizeableBuffer<Reallocator> {};
124
Brian Silvermanf51499a2020-09-21 12:49:08 -0700125} // namespace aos
126
127#endif // AOS_CONTAINERS_RESIZEABLE_BUFFER_H_