blob: 1af888db1676d1ed7b129e5933eead99f14cd905 [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
Stephan Pleines5fc35072024-05-22 17:33:18 -07004#include <stdint.h>
5#include <string.h>
6
7#include <algorithm>
Tyler Chatowbf0609c2021-07-31 16:13:27 -07008#include <cstdlib>
Brian Silvermanf51499a2020-09-21 12:49:08 -07009#include <memory>
10
11#include "glog/logging.h"
12
13namespace aos {
14
15// Kind of like a subset of vector<uint8_t>, but with less destructor calls.
16// When building unoptimized, especially with sanitizers, the vector<uint8_t>
17// version ends up being really slow in tests.
Austin Schuhbed6eeb2023-02-04 11:42:03 -080018//
19// F is the allocator used for reallocating the memory used by the buffer.
20template <class F>
21class AllocatorResizeableBuffer {
Brian Silvermanf51499a2020-09-21 12:49:08 -070022 public:
Austin Schuhbed6eeb2023-02-04 11:42:03 -080023 AllocatorResizeableBuffer() = default;
Brian Silvermanf51499a2020-09-21 12:49:08 -070024
Philipp Schrader790cb542023-07-05 21:06:52 -070025 AllocatorResizeableBuffer(const AllocatorResizeableBuffer &other) {
26 *this = other;
27 }
28 AllocatorResizeableBuffer(AllocatorResizeableBuffer &&other) {
29 *this = std::move(other);
30 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080031 AllocatorResizeableBuffer &operator=(const AllocatorResizeableBuffer &other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070032 resize(other.size());
33 memcpy(storage_.get(), other.storage_.get(), size());
34 return *this;
35 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080036 AllocatorResizeableBuffer &operator=(AllocatorResizeableBuffer &&other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070037 std::swap(storage_, other.storage_);
38 std::swap(size_, other.size_);
39 std::swap(capacity_, other.capacity_);
40 return *this;
41 }
42
43 uint8_t *data() { return static_cast<uint8_t *>(storage_.get()); }
44 const uint8_t *data() const {
45 return static_cast<const uint8_t *>(storage_.get());
46 }
47
48 uint8_t *begin() { return data(); }
49 const uint8_t *begin() const { return data(); }
50 uint8_t *end() { return data() + size(); }
51 const uint8_t *end() const { return data() + size(); }
Brennan Coslett7a8533f2023-04-13 13:08:17 -050052 uint8_t &at(int index) { return *(data() + index); }
53 const uint8_t &at(int index) const { return *(data() + index); }
Brian Silvermanf51499a2020-09-21 12:49:08 -070054
55 size_t size() const { return size_; }
56 size_t capacity() const { return capacity_; }
57
Austin Schuh3c9f92c2024-04-30 17:56:42 -070058 bool empty() const { return size_ == 0; }
59
Austin Schuh83548e92022-10-16 18:41:37 -070060 void reserve(size_t new_size) {
61 if (new_size > capacity_) {
62 Allocate(new_size);
63 }
64 }
65
Brian Silvermanf51499a2020-09-21 12:49:08 -070066 void resize(size_t new_size) {
67 if (new_size > capacity_) {
68 Allocate(new_size);
69 }
70 size_ = new_size;
71 }
72
73 void erase_front(size_t count) {
74 if (count == 0) {
75 return;
76 }
77 CHECK_LE(count, size_);
78 memmove(static_cast<void *>(data()), static_cast<void *>(data() + count),
79 size_ - count);
80 size_ -= count;
81 }
82
83 void push_back(uint8_t x) {
84 if (size_ == capacity_) {
85 Allocate(std::max<size_t>(16, size_ * 2));
86 }
87 *end() = x;
88 ++size_;
89 CHECK_LE(size_, capacity_);
90 }
91
92 private:
93 // We need this silly function because C++ is bad, and extensions to it which
94 // we work with make it a true nightmare.
95 //
96 // (a) You can't easily write out the signature of free because it depends on
97 // whether exceptions are enabled or not. You could use decltype, but see (b).
98 //
99 // (b) You can't easily write &free because CUDA overloads it with a
100 // __device__ version. You could cast to the appropriate version, but see (a).
101 //
102 // There's probably some kind of SFINAE thing which could find the matching
103 // signature from a set of choices, and then we could just
104 // static_cast<TheSignature>(&free). However, that sounds like a nightmare,
105 // especially because it has to conditionally enable the part mentioning CUDA
106 // identifiers in the preprocessor. This little function is way simpler.
107 static void DoFree(void *p) { free(p); }
108
109 void Allocate(size_t new_capacity) {
110 void *const old = storage_.release();
Austin Schuh6bdcc372024-06-27 14:49:11 -0700111 void *new_ptr = F::Realloc(old, capacity_, new_capacity);
112 CHECK(new_ptr != nullptr);
113 storage_.reset(new_ptr);
Brian Silvermanf51499a2020-09-21 12:49:08 -0700114 capacity_ = new_capacity;
115 }
116
117 std::unique_ptr<void, decltype(&DoFree)> storage_{nullptr, &DoFree};
118 size_t size_ = 0, capacity_ = 0;
119};
120
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800121// An allocator which just uses realloc to allocate.
122class Reallocator {
123 public:
124 static void *Realloc(void *old, size_t /*old_size*/, size_t new_capacity) {
125 return realloc(old, new_capacity);
126 }
127};
128
Austin Schuhd9336bc2024-04-29 18:41:23 -0700129// Allocates aligned memory.
130template <size_t alignment>
131class AlignedReallocator {
132 public:
133 static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
134 void *new_memory = std::aligned_alloc(alignment, new_capacity);
135 if (old) {
136 memcpy(new_memory, old, old_size);
137 free(old);
138 }
139 return new_memory;
140 }
141};
142
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800143// A resizable buffer which uses realloc when it needs to grow to attempt to
144// avoid full coppies.
145class ResizeableBuffer : public AllocatorResizeableBuffer<Reallocator> {};
146
Brian Silvermanf51499a2020-09-21 12:49:08 -0700147} // namespace aos
148
149#endif // AOS_CONTAINERS_RESIZEABLE_BUFFER_H_