blob: b3c3ef065e65ac77694f745eb3efd7af8e094dd7 [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#ifndef AOS_FLATBUFFERS_BASE_H_
2#define AOS_FLATBUFFERS_BASE_H_
Austin Schuh3c9f92c2024-04-30 17:56:42 -07003
Stephan Pleines6191f1d2024-05-30 20:44:45 -07004#include <stdint.h>
5#include <sys/types.h>
6
7#include <cstring>
James Kuszmaulf5eb4682023-09-22 17:16:59 -07008#include <memory>
9#include <optional>
Stephan Pleines6191f1d2024-05-30 20:44:45 -070010#include <ostream>
James Kuszmaulf5eb4682023-09-22 17:16:59 -070011#include <span>
Stephan Pleines6191f1d2024-05-30 20:44:45 -070012#include <utility>
13#include <vector>
James Kuszmaulf5eb4682023-09-22 17:16:59 -070014
Austin Schuh02e0d772024-05-30 16:41:06 -070015#include "absl/types/span.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070016#include "flatbuffers/base.h"
17#include "glog/logging.h"
Stephan Pleines6191f1d2024-05-30 20:44:45 -070018
Austin Schuh02e0d772024-05-30 16:41:06 -070019#include "aos/containers/resizeable_buffer.h"
20#include "aos/ipc_lib/data_alignment.h"
21
22namespace aos {
23using SharedSpan = std::shared_ptr<const absl::Span<const uint8_t>>;
24
25namespace fbs {
Austin Schuh3c9f92c2024-04-30 17:56:42 -070026
James Kuszmaulf5eb4682023-09-22 17:16:59 -070027using ::flatbuffers::soffset_t;
28using ::flatbuffers::uoffset_t;
29using ::flatbuffers::voffset_t;
30
31// Returns the smallest multiple of alignment that is greater than or equal to
32// size.
33constexpr size_t PaddedSize(size_t size, size_t alignment) {
34 // We can be clever with bitwise operations by assuming that aligment is a
35 // power of two. Or we can just be clearer about what we mean and eat a few
36 // integer divides.
37 return (((size - 1) / alignment) + 1) * alignment;
38}
39
40// Used as a parameter to methods where we are messing with memory and may or
41// may not want to clear it to zeroes.
42enum class SetZero { kYes, kNo };
43
44class Allocator;
45
46// Parent type of any object that may need to dynamically change size at
47// runtime. Used by the static table and vector types to request additional
48// blocks of memory when needed.
49//
50// The way that this works is that every ResizeableObject has some number of
51// children that are themselves ResizeableObject's and whose memory is entirely
52// contained within their parent's memory. A ResizeableObject without a parent
53// instead has an Allocator that it can use to allocate additional blocks
54// of memory. Whenever a child needs to grow in size, it will make a call to
55// InsertBytes() on its parent, which will percolate up until InsertBytes() gets
56// called on the root allocator. If the insert succeeds, then every single child
57// through the entire tree will get notified (this is because the allocator may
58// have shifted the entire memory buffer, so any pointers may need to be
59// updated). Child types will provide implementations of the GetObjects() method
60// to both allow tree traversal as well as to allow the various internal offsets
61// to be updated appropriately.
62class ResizeableObject {
63 public:
64 // Returns the underlying memory buffer into which the flatbuffer will be
65 // serialized.
66 std::span<uint8_t> buffer() { return buffer_; }
67 std::span<const uint8_t> buffer() const { return buffer_; }
68
69 // Updates the underlying memory buffer to new_buffer, with an indication of
70 // where bytes were inserted/removed from the buffer. It is assumed that
71 // new_buffer already has the state of the serialized flatbuffer
72 // copied into it.
73 // * When bytes have been inserted, modification_point will point to the first
74 // of the inserted bytes in new_buffer and bytes_inserted will be the number
75 // of new bytes.
76 // * Buffer shrinkage is not currently supported.
77 // * When bytes_inserted is zero, modification_point is ignored.
78 void UpdateBuffer(std::span<uint8_t> new_buffer, void *modification_point,
79 ssize_t bytes_inserted);
80
81 protected:
82 // Data associated with a sub-object of this object.
83 struct SubObject {
84 // A direct pointer to the inline entry in the flatbuffer table data. The
85 // pointer must be valid, but the entry itself may be zero if the object is
86 // not actually populated.
87 // If *inline_entry is non-zero, this will get updated if any new memory got
88 // added/removed in-between inline_entry and the actual data pointed to be
89 // inline_entry.
90 uoffset_t *inline_entry;
91 // The actual child object. Should be nullptr if *inline_entry is zero; must
92 // be valid if *inline_entry is non-zero.
93 ResizeableObject *object;
94 // The nominal offset from buffer_.data() to object->buffer_.data().
95 // Must be provided, and must always be valid, even if *inline_entry is
96 // zero.
97 // I.e., the following holds when object is not nullptr:
98 // SubObject object = parent.GetSubObject(index);
99 // CHECK_EQ(parent.buffer()->data() + *object.absolute_offset,
100 // object.object->buffer().data());
101 size_t *absolute_offset;
102 };
103
104 ResizeableObject(std::span<uint8_t> buffer, ResizeableObject *parent)
105 : buffer_(buffer), parent_(parent) {}
106 ResizeableObject(std::span<uint8_t> buffer, Allocator *allocator)
107 : buffer_(buffer), allocator_(allocator) {}
108 ResizeableObject(std::span<uint8_t> buffer,
109 std::unique_ptr<Allocator> allocator)
110 : buffer_(buffer),
111 owned_allocator_(std::move(allocator)),
112 allocator_(owned_allocator_.get()) {}
113 ResizeableObject(const ResizeableObject &) = delete;
114 ResizeableObject &operator=(const ResizeableObject &) = delete;
115 // Users do not end up using the move constructor; however, it is needed to
116 // handle the fact that a ResizeableObject may be a member of an std::vector
117 // in the various generated types.
James Kuszmauld4b4f1d2024-03-13 15:57:35 -0700118 ResizeableObject(ResizeableObject &&other);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700119 // Required alignment of this object.
120 virtual size_t Alignment() const = 0;
121 // Offset from the start of buffer() to the actual start of the object in
122 // question (this is important for vectors, where the vector itself cannot
123 // have internal padding, and so the start of the vector may be offset from
124 // the start of the buffer to handle alignment).
125 virtual size_t AbsoluteOffsetOffset() const = 0;
126 // Causes bytes bytes to be inserted between insertion_point - 1 and
127 // insertion_point.
128 // If requested, the new bytes will be cleared to zero; otherwise they will be
129 // left uninitialized.
130 // The insertion_point may not be equal to this->buffer_.data(); it may be a
131 // pointer just past the end of the buffer. This is to ease the
132 // implementation, and is merely a requirement that any buffer growth occur
133 // only on the inside or past the end of the vector, and not prior to the
134 // start of the vector.
135 // Returns true on success, false on failure (e.g., if the allocator has no
136 // memory available).
137 bool InsertBytes(void *insertion_point, size_t bytes, SetZero set_zero);
138 // Called *after* the internal buffer_ has been swapped out and *after* the
139 // object tree has been traversed and fixed.
140 virtual void ObserveBufferModification() {}
141
142 // Returns the index'th sub object of this object.
143 // index must be less than NumberOfSubObjects().
144 // This will include objects which are not currently populated but which may
145 // be populated in the future (so that we can track what the necessary offsets
146 // are when we do populate it).
147 virtual SubObject GetSubObject(size_t index) = 0;
148 // Number of sub-objects of this object. May be zero.
149 virtual size_t NumberOfSubObjects() const = 0;
150
151 // Treating the supplied absolute_offset as an offset into the internal memory
152 // buffer, return the pointer to the underlying memory.
153 const void *PointerForAbsoluteOffset(const size_t absolute_offset) {
154 return buffer_.data() + absolute_offset;
155 }
156 // Returns a span at the requested offset into the buffer. terminal_alignment
157 // does not align the start of the buffer; instead, it ensures that the memory
158 // from absolute_offset + size until the next multiple of terminal_alignment
159 // is set to all zeroes.
160 std::span<uint8_t> BufferForObject(size_t absolute_offset, size_t size,
161 size_t terminal_alignment);
162 // When memory has been inserted/removed, this iterates over the sub-objects
163 // and notifies/adjusts them appropriately.
164 // This will be called after buffer_ has been updated, and:
165 // * For insertion, modification_point will point into the new buffer_ to the
166 // first of the newly inserted bytes.
167 // * Removal is not entirely implemented yet, but for removal,
168 // modification_point should point to the first byte after the removed
169 // chunk.
170 void FixObjects(void *modification_point, ssize_t bytes_inserted);
171
172 Allocator *allocator() { return allocator_; }
173
174 std::span<uint8_t> buffer_;
175
176 private:
177 ResizeableObject *parent_ = nullptr;
178 std::unique_ptr<Allocator> owned_allocator_;
179 Allocator *allocator_ = nullptr;
180};
181
182// Interface to represent a memory allocator for use with ResizeableObject.
183class Allocator {
184 public:
185 virtual ~Allocator() {}
Austin Schuh02e0d772024-05-30 16:41:06 -0700186 // Allocates memory of the requested size and alignment. alignment is
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700187 // guaranteed.
Austin Schuh02e0d772024-05-30 16:41:06 -0700188 //
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700189 // On failure to allocate the requested size, returns nullopt;
190 // Never returns a partial span.
191 // The span will be initialized to zero upon request.
192 // Once Allocate() has been called once, it may not be called again until
193 // Deallocate() has been called. In order to adjust the size of the buffer,
194 // call InsertBytes() and RemoveBytes().
195 [[nodiscard]] virtual std::optional<std::span<uint8_t>> Allocate(
Austin Schuh02e0d772024-05-30 16:41:06 -0700196 size_t size, size_t alignment, SetZero set_zero) = 0;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700197 // Identical to Allocate(), but dies on failure.
Austin Schuh02e0d772024-05-30 16:41:06 -0700198 [[nodiscard]] std::span<uint8_t> AllocateOrDie(size_t size, size_t alignment,
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700199 SetZero set_zero) {
200 std::optional<std::span<uint8_t>> span =
Austin Schuh02e0d772024-05-30 16:41:06 -0700201 Allocate(size, alignment, set_zero);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700202 CHECK(span.has_value()) << ": Failed to allocate " << size << " bytes.";
203 CHECK_EQ(size, span.value().size())
204 << ": Failed to allocate " << size << " bytes.";
Austin Schuh02e0d772024-05-30 16:41:06 -0700205 CHECK_EQ(reinterpret_cast<size_t>(span.value().data()) % alignment, 0u)
206 << "Failed to allocate data of length " << size << " with alignment "
207 << alignment;
208
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700209 return span.value();
210 }
211 // Increases the size of the buffer by inserting bytes bytes immediately
212 // before insertion_point.
213 // alignment_hint specifies the alignment of the entire buffer, not of the
214 // inserted bytes.
215 // The returned span may or may not overlap with the original buffer in
216 // memory.
217 // The inserted bytes will be set to zero or uninitialized depending on the
218 // value of SetZero.
219 // insertion_point must be in (or 1 past the end of) the buffer.
220 // Returns nullopt on a failure to allocate the requested bytes.
221 [[nodiscard]] virtual std::optional<std::span<uint8_t>> InsertBytes(
222 void *insertion_point, size_t bytes, size_t alignment_hint,
223 SetZero set_zero) = 0;
224 // Removes the requested span of bytes from the buffer, returning the new
225 // buffer.
226 [[nodiscard]] virtual std::span<uint8_t> RemoveBytes(
227 std::span<uint8_t> remove_bytes) = 0;
228 // Deallocates the currently allocated buffer. The provided buffer must match
229 // the latest version of the buffer.
230 // If Allocate() has been called, Deallocate() must be called prior to
231 // destroying the Allocator.
232 virtual void Deallocate(std::span<uint8_t> buffer) = 0;
233};
234
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700235// Allocator that allocates all of its memory within a provided span. To match
236// the behavior of the FlatBufferBuilder, it will start its allocations at the
237// end of the provided span.
238//
239// Attempts to allocate more memory than is present in the provided buffer will
240// fail.
241class SpanAllocator : public Allocator {
242 public:
243 SpanAllocator(std::span<uint8_t> buffer) : buffer_(buffer) {}
244 ~SpanAllocator() {
245 CHECK(!allocated_)
246 << ": Must deallocate before destroying the SpanAllocator.";
247 }
248
249 std::optional<std::span<uint8_t>> Allocate(size_t size, size_t /*alignment*/,
250 SetZero set_zero) override;
251
252 std::optional<std::span<uint8_t>> InsertBytes(void *insertion_point,
253 size_t bytes,
254 size_t /*alignment*/,
255 SetZero set_zero) override;
256
257 std::span<uint8_t> RemoveBytes(std::span<uint8_t> remove_bytes) override;
258
259 void Deallocate(std::span<uint8_t>) override;
260
261 private:
262 std::span<uint8_t> buffer_;
263 bool allocated_ = false;
264 size_t allocated_size_ = 0;
265};
266
Austin Schuh02e0d772024-05-30 16:41:06 -0700267// Allocator that uses an AllocatorResizeableBuffer to allow arbitrary-sized
268// allocations. Aligns the end of the buffer to an alignment of
269// kChannelDataAlignment.
270class AlignedVectorAllocator : public fbs::Allocator {
271 public:
272 static constexpr size_t kAlignment = aos::kChannelDataAlignment;
273 AlignedVectorAllocator() {}
274 ~AlignedVectorAllocator();
275
276 std::optional<std::span<uint8_t>> Allocate(size_t size, size_t alignment,
277 fbs::SetZero set_zero) override;
278
279 std::optional<std::span<uint8_t>> InsertBytes(void *insertion_point,
280 size_t bytes, size_t alignment,
281 fbs::SetZero set_zero) override;
282
283 std::span<uint8_t> RemoveBytes(std::span<uint8_t> remove_bytes) override;
284
285 void Deallocate(std::span<uint8_t>) override;
286
287 aos::SharedSpan Release();
288
289 private:
290 struct SharedSpanHolder {
291 aos::AllocatorResizeableBuffer<aos::AlignedReallocator<kAlignment>> buffer;
292 absl::Span<const uint8_t> span;
293 };
294 uint8_t *data() { return buffer_.data() + buffer_.size() - allocated_size_; }
295
296 aos::AllocatorResizeableBuffer<aos::AlignedReallocator<kAlignment>> buffer_;
297
298 size_t allocated_size_ = 0u;
299 bool released_ = false;
300};
301
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800302// Allocates and owns a fixed-size memory buffer on the stack.
303//
304// This provides a convenient Allocator for use with the aos::fbs::Builder
305// in realtime code instead of trying to use the VectorAllocator.
Austin Schuh02e0d772024-05-30 16:41:06 -0700306template <std::size_t N, std::size_t alignment = 64>
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800307class FixedStackAllocator : public SpanAllocator {
308 public:
309 FixedStackAllocator() : SpanAllocator({buffer_, sizeof(buffer_)}) {}
310
311 private:
Austin Schuh02e0d772024-05-30 16:41:06 -0700312 alignas(alignment) uint8_t buffer_[N];
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800313};
314
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700315namespace internal {
316std::ostream &DebugBytes(std::span<const uint8_t> span, std::ostream &os);
317inline void ClearSpan(std::span<uint8_t> span) {
318 memset(span.data(), 0, span.size());
319}
320// std::span::subspan does not do bounds checking.
321template <typename T>
322inline std::span<T> GetSubSpan(std::span<T> span, size_t offset,
323 size_t count = std::dynamic_extent) {
324 if (count != std::dynamic_extent) {
325 CHECK_LE(offset + count, span.size());
326 }
327 return span.subspan(offset, count);
328}
329// Normal users should never move any of the special flatbuffer types that we
330// provide. However, they do need to be moveable in order to support the use of
331// resizeable vectors. As such, we make all the move constructors private and
332// friend the TableMover struct. The TableMover struct is then used in places
333// that need to have moveable objects. It should never be used by a user.
334template <typename T>
335struct TableMover {
336 TableMover(std::span<uint8_t> buffer, ResizeableObject *parent)
337 : t(buffer, parent) {}
338 TableMover(std::span<uint8_t> buffer, Allocator *allocator)
339 : t(buffer, allocator) {}
340 TableMover(std::span<uint8_t> buffer, ::std::unique_ptr<Allocator> allocator)
341 : t(buffer, ::std::move(allocator)) {}
342 TableMover(TableMover &&) = default;
343 T t;
344};
345} // namespace internal
Austin Schuh02e0d772024-05-30 16:41:06 -0700346} // namespace fbs
347} // namespace aos
348
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700349#endif // AOS_FLATBUFFERS_BASE_H_