blob: db4a102f90a10abe18cc3dd25d4c08f13d4ec327 [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
Austin Schuh99f7c6a2024-06-25 22:07:44 -070011#include "absl/log/check.h"
12#include "absl/log/log.h"
Brian Silvermanf51499a2020-09-21 12:49:08 -070013
14namespace aos {
15
16// Kind of like a subset of vector<uint8_t>, but with less destructor calls.
17// When building unoptimized, especially with sanitizers, the vector<uint8_t>
18// version ends up being really slow in tests.
Austin Schuhbed6eeb2023-02-04 11:42:03 -080019//
20// F is the allocator used for reallocating the memory used by the buffer.
21template <class F>
22class AllocatorResizeableBuffer {
Brian Silvermanf51499a2020-09-21 12:49:08 -070023 public:
Austin Schuhbed6eeb2023-02-04 11:42:03 -080024 AllocatorResizeableBuffer() = default;
Brian Silvermanf51499a2020-09-21 12:49:08 -070025
Philipp Schrader790cb542023-07-05 21:06:52 -070026 AllocatorResizeableBuffer(const AllocatorResizeableBuffer &other) {
27 *this = other;
28 }
29 AllocatorResizeableBuffer(AllocatorResizeableBuffer &&other) {
30 *this = std::move(other);
31 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080032 AllocatorResizeableBuffer &operator=(const AllocatorResizeableBuffer &other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070033 resize(other.size());
34 memcpy(storage_.get(), other.storage_.get(), size());
35 return *this;
36 }
Austin Schuhbed6eeb2023-02-04 11:42:03 -080037 AllocatorResizeableBuffer &operator=(AllocatorResizeableBuffer &&other) {
Brian Silvermanf51499a2020-09-21 12:49:08 -070038 std::swap(storage_, other.storage_);
39 std::swap(size_, other.size_);
40 std::swap(capacity_, other.capacity_);
41 return *this;
42 }
43
44 uint8_t *data() { return static_cast<uint8_t *>(storage_.get()); }
45 const uint8_t *data() const {
46 return static_cast<const uint8_t *>(storage_.get());
47 }
48
49 uint8_t *begin() { return data(); }
50 const uint8_t *begin() const { return data(); }
51 uint8_t *end() { return data() + size(); }
52 const uint8_t *end() const { return data() + size(); }
Brennan Coslett7a8533f2023-04-13 13:08:17 -050053 uint8_t &at(int index) { return *(data() + index); }
54 const uint8_t &at(int index) const { return *(data() + index); }
Brian Silvermanf51499a2020-09-21 12:49:08 -070055
56 size_t size() const { return size_; }
57 size_t capacity() const { return capacity_; }
58
Austin Schuh3c9f92c2024-04-30 17:56:42 -070059 bool empty() const { return size_ == 0; }
60
Austin Schuh83548e92022-10-16 18:41:37 -070061 void reserve(size_t new_size) {
62 if (new_size > capacity_) {
63 Allocate(new_size);
64 }
65 }
66
Brian Silvermanf51499a2020-09-21 12:49:08 -070067 void resize(size_t new_size) {
68 if (new_size > capacity_) {
69 Allocate(new_size);
70 }
71 size_ = new_size;
72 }
73
74 void erase_front(size_t count) {
75 if (count == 0) {
76 return;
77 }
78 CHECK_LE(count, size_);
79 memmove(static_cast<void *>(data()), static_cast<void *>(data() + count),
80 size_ - count);
81 size_ -= count;
82 }
83
84 void push_back(uint8_t x) {
85 if (size_ == capacity_) {
86 Allocate(std::max<size_t>(16, size_ * 2));
87 }
88 *end() = x;
89 ++size_;
90 CHECK_LE(size_, capacity_);
91 }
92
93 private:
94 // We need this silly function because C++ is bad, and extensions to it which
95 // we work with make it a true nightmare.
96 //
97 // (a) You can't easily write out the signature of free because it depends on
98 // whether exceptions are enabled or not. You could use decltype, but see (b).
99 //
100 // (b) You can't easily write &free because CUDA overloads it with a
101 // __device__ version. You could cast to the appropriate version, but see (a).
102 //
103 // There's probably some kind of SFINAE thing which could find the matching
104 // signature from a set of choices, and then we could just
105 // static_cast<TheSignature>(&free). However, that sounds like a nightmare,
106 // especially because it has to conditionally enable the part mentioning CUDA
107 // identifiers in the preprocessor. This little function is way simpler.
108 static void DoFree(void *p) { free(p); }
109
110 void Allocate(size_t new_capacity) {
111 void *const old = storage_.release();
Austin Schuh6bdcc372024-06-27 14:49:11 -0700112 void *new_ptr = F::Realloc(old, capacity_, new_capacity);
113 CHECK(new_ptr != nullptr);
114 storage_.reset(new_ptr);
Brian Silvermanf51499a2020-09-21 12:49:08 -0700115 capacity_ = new_capacity;
116 }
117
118 std::unique_ptr<void, decltype(&DoFree)> storage_{nullptr, &DoFree};
119 size_t size_ = 0, capacity_ = 0;
120};
121
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800122// An allocator which just uses realloc to allocate.
123class Reallocator {
124 public:
125 static void *Realloc(void *old, size_t /*old_size*/, size_t new_capacity) {
126 return realloc(old, new_capacity);
127 }
128};
129
Austin Schuhd9336bc2024-04-29 18:41:23 -0700130// Allocates aligned memory.
131template <size_t alignment>
132class AlignedReallocator {
133 public:
134 static void *Realloc(void *old, size_t old_size, size_t new_capacity) {
135 void *new_memory = std::aligned_alloc(alignment, new_capacity);
136 if (old) {
137 memcpy(new_memory, old, old_size);
138 free(old);
139 }
140 return new_memory;
141 }
142};
143
Austin Schuhbed6eeb2023-02-04 11:42:03 -0800144// A resizable buffer which uses realloc when it needs to grow to attempt to
145// avoid full coppies.
146class ResizeableBuffer : public AllocatorResizeableBuffer<Reallocator> {};
147
Brian Silvermanf51499a2020-09-21 12:49:08 -0700148} // namespace aos
149
150#endif // AOS_CONTAINERS_RESIZEABLE_BUFFER_H_