blob: 484d93ea8e5b11dffeecc1132f5c92662b8db9ad [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 Schuh83548e92022-10-16 18:41:37 -070058 void reserve(size_t new_size) {
59 if (new_size > capacity_) {
60 Allocate(new_size);
61 }
62 }
63
Brian Silvermanf51499a2020-09-21 12:49:08 -070064 void resize(size_t new_size) {
65 if (new_size > capacity_) {
66 Allocate(new_size);
67 }
68 size_ = new_size;
69 }
70
71 void erase_front(size_t count) {
72 if (count == 0) {
73 return;
74 }
75 CHECK_LE(count, size_);
76 memmove(static_cast<void *>(data()), static_cast<void *>(data() + count),
77 size_ - count);
78 size_ -= count;
79 }
80
81 void push_back(uint8_t x) {
82 if (size_ == capacity_) {
83 Allocate(std::max<size_t>(16, size_ * 2));
84 }
85 *end() = x;
86 ++size_;
87 CHECK_LE(size_, capacity_);
88 }
89
90 private:
91 // We need this silly function because C++ is bad, and extensions to it which
92 // we work with make it a true nightmare.
93 //
94 // (a) You can't easily write out the signature of free because it depends on
95 // whether exceptions are enabled or not. You could use decltype, but see (b).
96 //
97 // (b) You can't easily write &free because CUDA overloads it with a
98 // __device__ version. You could cast to the appropriate version, but see (a).
99 //
100 // There's probably some kind of SFINAE thing which could find the matching
101 // signature from a set of choices, and then we could just
102 // static_cast<TheSignature>(&free). However, that sounds like a nightmare,
103 // especially because it has to conditionally enable the part mentioning CUDA
104 // identifiers in the preprocessor. This little function is way simpler.
105 static void DoFree(void *p) { free(p); }
106
107 void Allocate(size_t new_capacity) {
108 void *const old = storage_.release();
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800109 storage_.reset(CHECK_NOTNULL(F::Realloc(old, capacity_, new_capacity)));
Brian Silvermanf51499a2020-09-21 12:49:08 -0700110 capacity_ = new_capacity;
111 }
112
113 std::unique_ptr<void, decltype(&DoFree)> storage_{nullptr, &DoFree};
114 size_t size_ = 0, capacity_ = 0;
115};
116
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800117// An allocator which just uses realloc to allocate.
118class Reallocator {
119 public:
120 static void *Realloc(void *old, size_t /*old_size*/, size_t new_capacity) {
121 return realloc(old, new_capacity);
122 }
123};
124
Austin Schuhd9336bc2024-04-29 18:41:23 -0700125// Allocates aligned memory.
126template <size_t alignment>
127class AlignedReallocator {
128 public:
129 static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
130 void *new_memory = std::aligned_alloc(alignment, new_capacity);
131 if (old) {
132 memcpy(new_memory, old, old_size);
133 free(old);
134 }
135 return new_memory;
136 }
137};
138
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800139// A resizable buffer which uses realloc when it needs to grow to attempt to
140// avoid full coppies.
141class ResizeableBuffer : public AllocatorResizeableBuffer<Reallocator> {};
142
Brian Silvermanf51499a2020-09-21 12:49:08 -0700143} // namespace aos
144
145#endif // AOS_CONTAINERS_RESIZEABLE_BUFFER_H_