blob: 045fce34c71871e8de1448ca735217d53f8d4383 [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"
Austin Schuh8a399de2024-06-05 10:46:23 -070021#include "aos/shared_span.h"
Austin Schuh02e0d772024-05-30 16:41:06 -070022
Austin Schuh8a399de2024-06-05 10:46:23 -070023namespace aos::fbs {
Austin Schuh3c9f92c2024-04-30 17:56:42 -070024
James Kuszmaulf5eb4682023-09-22 17:16:59 -070025using ::flatbuffers::soffset_t;
26using ::flatbuffers::uoffset_t;
27using ::flatbuffers::voffset_t;
28
Austin Schuhf8440852024-05-31 10:46:50 -070029// Returns the offset into the buffer needed to provide 'alignment' alignment
30// 'aligned_offset' bytes after the returned offset. This assumes that the
31// first 'starting_offset' bytes are spoken for.
32constexpr size_t AlignOffset(size_t starting_offset, size_t alignment,
33 size_t aligned_offset = 0) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -070034 // 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.
Austin Schuhf8440852024-05-31 10:46:50 -070037 return (((starting_offset + aligned_offset - 1) / alignment) + 1) *
38 alignment -
39 aligned_offset;
James Kuszmaulf5eb4682023-09-22 17:16:59 -070040}
41
42// Used as a parameter to methods where we are messing with memory and may or
43// may not want to clear it to zeroes.
44enum class SetZero { kYes, kNo };
45
46class Allocator;
47
48// Parent type of any object that may need to dynamically change size at
49// runtime. Used by the static table and vector types to request additional
50// blocks of memory when needed.
51//
52// The way that this works is that every ResizeableObject has some number of
53// children that are themselves ResizeableObject's and whose memory is entirely
54// contained within their parent's memory. A ResizeableObject without a parent
55// instead has an Allocator that it can use to allocate additional blocks
56// of memory. Whenever a child needs to grow in size, it will make a call to
57// InsertBytes() on its parent, which will percolate up until InsertBytes() gets
58// called on the root allocator. If the insert succeeds, then every single child
59// through the entire tree will get notified (this is because the allocator may
60// have shifted the entire memory buffer, so any pointers may need to be
61// updated). Child types will provide implementations of the GetObjects() method
62// to both allow tree traversal as well as to allow the various internal offsets
63// to be updated appropriately.
64class ResizeableObject {
65 public:
66 // Returns the underlying memory buffer into which the flatbuffer will be
67 // serialized.
68 std::span<uint8_t> buffer() { return buffer_; }
69 std::span<const uint8_t> buffer() const { return buffer_; }
70
71 // Updates the underlying memory buffer to new_buffer, with an indication of
72 // where bytes were inserted/removed from the buffer. It is assumed that
73 // new_buffer already has the state of the serialized flatbuffer
74 // copied into it.
75 // * When bytes have been inserted, modification_point will point to the first
76 // of the inserted bytes in new_buffer and bytes_inserted will be the number
77 // of new bytes.
78 // * Buffer shrinkage is not currently supported.
79 // * When bytes_inserted is zero, modification_point is ignored.
80 void UpdateBuffer(std::span<uint8_t> new_buffer, void *modification_point,
81 ssize_t bytes_inserted);
82
83 protected:
84 // Data associated with a sub-object of this object.
85 struct SubObject {
86 // A direct pointer to the inline entry in the flatbuffer table data. The
87 // pointer must be valid, but the entry itself may be zero if the object is
88 // not actually populated.
89 // If *inline_entry is non-zero, this will get updated if any new memory got
90 // added/removed in-between inline_entry and the actual data pointed to be
91 // inline_entry.
92 uoffset_t *inline_entry;
93 // The actual child object. Should be nullptr if *inline_entry is zero; must
94 // be valid if *inline_entry is non-zero.
95 ResizeableObject *object;
96 // The nominal offset from buffer_.data() to object->buffer_.data().
97 // Must be provided, and must always be valid, even if *inline_entry is
98 // zero.
99 // I.e., the following holds when object is not nullptr:
100 // SubObject object = parent.GetSubObject(index);
101 // CHECK_EQ(parent.buffer()->data() + *object.absolute_offset,
102 // object.object->buffer().data());
103 size_t *absolute_offset;
104 };
105
106 ResizeableObject(std::span<uint8_t> buffer, ResizeableObject *parent)
107 : buffer_(buffer), parent_(parent) {}
108 ResizeableObject(std::span<uint8_t> buffer, Allocator *allocator)
109 : buffer_(buffer), allocator_(allocator) {}
110 ResizeableObject(std::span<uint8_t> buffer,
111 std::unique_ptr<Allocator> allocator)
112 : buffer_(buffer),
113 owned_allocator_(std::move(allocator)),
114 allocator_(owned_allocator_.get()) {}
115 ResizeableObject(const ResizeableObject &) = delete;
116 ResizeableObject &operator=(const ResizeableObject &) = delete;
117 // Users do not end up using the move constructor; however, it is needed to
118 // handle the fact that a ResizeableObject may be a member of an std::vector
119 // in the various generated types.
James Kuszmauld4b4f1d2024-03-13 15:57:35 -0700120 ResizeableObject(ResizeableObject &&other);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700121 // Required alignment of this object.
122 virtual size_t Alignment() const = 0;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700123 // Causes bytes bytes to be inserted between insertion_point - 1 and
124 // insertion_point.
125 // If requested, the new bytes will be cleared to zero; otherwise they will be
126 // left uninitialized.
127 // The insertion_point may not be equal to this->buffer_.data(); it may be a
128 // pointer just past the end of the buffer. This is to ease the
129 // implementation, and is merely a requirement that any buffer growth occur
130 // only on the inside or past the end of the vector, and not prior to the
131 // start of the vector.
Austin Schuhf8440852024-05-31 10:46:50 -0700132 // Returns a span of the inserted bytes on success, nullopt on failure (e.g.,
133 // if the allocator has no memory available).
134 std::optional<std::span<uint8_t>> InsertBytes(void *insertion_point,
135 size_t bytes, SetZero set_zero);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700136 // Called *after* the internal buffer_ has been swapped out and *after* the
137 // object tree has been traversed and fixed.
138 virtual void ObserveBufferModification() {}
139
140 // Returns the index'th sub object of this object.
141 // index must be less than NumberOfSubObjects().
142 // This will include objects which are not currently populated but which may
143 // be populated in the future (so that we can track what the necessary offsets
144 // are when we do populate it).
145 virtual SubObject GetSubObject(size_t index) = 0;
146 // Number of sub-objects of this object. May be zero.
147 virtual size_t NumberOfSubObjects() const = 0;
148
149 // Treating the supplied absolute_offset as an offset into the internal memory
150 // buffer, return the pointer to the underlying memory.
151 const void *PointerForAbsoluteOffset(const size_t absolute_offset) {
152 return buffer_.data() + absolute_offset;
153 }
Austin Schuhf8440852024-05-31 10:46:50 -0700154 // Returns a span at the requested offset into the buffer for the requested
155 // size.
156 std::span<uint8_t> BufferForObject(size_t absolute_offset, size_t size);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700157 // When memory has been inserted/removed, this iterates over the sub-objects
158 // and notifies/adjusts them appropriately.
159 // This will be called after buffer_ has been updated, and:
160 // * For insertion, modification_point will point into the new buffer_ to the
161 // first of the newly inserted bytes.
162 // * Removal is not entirely implemented yet, but for removal,
163 // modification_point should point to the first byte after the removed
164 // chunk.
165 void FixObjects(void *modification_point, ssize_t bytes_inserted);
166
167 Allocator *allocator() { return allocator_; }
168
169 std::span<uint8_t> buffer_;
170
171 private:
172 ResizeableObject *parent_ = nullptr;
173 std::unique_ptr<Allocator> owned_allocator_;
174 Allocator *allocator_ = nullptr;
175};
176
177// Interface to represent a memory allocator for use with ResizeableObject.
178class Allocator {
179 public:
180 virtual ~Allocator() {}
Austin Schuh02e0d772024-05-30 16:41:06 -0700181 // Allocates memory of the requested size and alignment. alignment is
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700182 // guaranteed.
Austin Schuh02e0d772024-05-30 16:41:06 -0700183 //
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700184 // On failure to allocate the requested size, returns nullopt;
185 // Never returns a partial span.
186 // The span will be initialized to zero upon request.
187 // Once Allocate() has been called once, it may not be called again until
188 // Deallocate() has been called. In order to adjust the size of the buffer,
189 // call InsertBytes() and RemoveBytes().
190 [[nodiscard]] virtual std::optional<std::span<uint8_t>> Allocate(
Austin Schuh02e0d772024-05-30 16:41:06 -0700191 size_t size, size_t alignment, SetZero set_zero) = 0;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700192 // Identical to Allocate(), but dies on failure.
Austin Schuh02e0d772024-05-30 16:41:06 -0700193 [[nodiscard]] std::span<uint8_t> AllocateOrDie(size_t size, size_t alignment,
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700194 SetZero set_zero) {
195 std::optional<std::span<uint8_t>> span =
Austin Schuh02e0d772024-05-30 16:41:06 -0700196 Allocate(size, alignment, set_zero);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700197 CHECK(span.has_value()) << ": Failed to allocate " << size << " bytes.";
198 CHECK_EQ(size, span.value().size())
199 << ": Failed to allocate " << size << " bytes.";
Austin Schuh02e0d772024-05-30 16:41:06 -0700200 CHECK_EQ(reinterpret_cast<size_t>(span.value().data()) % alignment, 0u)
201 << "Failed to allocate data of length " << size << " with alignment "
202 << alignment;
203
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700204 return span.value();
205 }
206 // Increases the size of the buffer by inserting bytes bytes immediately
207 // before insertion_point.
208 // alignment_hint specifies the alignment of the entire buffer, not of the
209 // inserted bytes.
210 // The returned span may or may not overlap with the original buffer in
211 // memory.
212 // The inserted bytes will be set to zero or uninitialized depending on the
213 // value of SetZero.
214 // insertion_point must be in (or 1 past the end of) the buffer.
215 // Returns nullopt on a failure to allocate the requested bytes.
216 [[nodiscard]] virtual std::optional<std::span<uint8_t>> InsertBytes(
217 void *insertion_point, size_t bytes, size_t alignment_hint,
218 SetZero set_zero) = 0;
219 // Removes the requested span of bytes from the buffer, returning the new
220 // buffer.
221 [[nodiscard]] virtual std::span<uint8_t> RemoveBytes(
222 std::span<uint8_t> remove_bytes) = 0;
223 // Deallocates the currently allocated buffer. The provided buffer must match
224 // the latest version of the buffer.
225 // If Allocate() has been called, Deallocate() must be called prior to
226 // destroying the Allocator.
227 virtual void Deallocate(std::span<uint8_t> buffer) = 0;
228};
229
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700230// Allocator that allocates all of its memory within a provided span. To match
231// the behavior of the FlatBufferBuilder, it will start its allocations at the
232// end of the provided span.
233//
234// Attempts to allocate more memory than is present in the provided buffer will
235// fail.
236class SpanAllocator : public Allocator {
237 public:
238 SpanAllocator(std::span<uint8_t> buffer) : buffer_(buffer) {}
239 ~SpanAllocator() {
240 CHECK(!allocated_)
241 << ": Must deallocate before destroying the SpanAllocator.";
242 }
243
244 std::optional<std::span<uint8_t>> Allocate(size_t size, size_t /*alignment*/,
245 SetZero set_zero) override;
246
247 std::optional<std::span<uint8_t>> InsertBytes(void *insertion_point,
248 size_t bytes,
249 size_t /*alignment*/,
250 SetZero set_zero) override;
251
252 std::span<uint8_t> RemoveBytes(std::span<uint8_t> remove_bytes) override;
253
254 void Deallocate(std::span<uint8_t>) override;
255
256 private:
257 std::span<uint8_t> buffer_;
258 bool allocated_ = false;
259 size_t allocated_size_ = 0;
260};
261
Austin Schuh02e0d772024-05-30 16:41:06 -0700262// Allocator that uses an AllocatorResizeableBuffer to allow arbitrary-sized
263// allocations. Aligns the end of the buffer to an alignment of
264// kChannelDataAlignment.
265class AlignedVectorAllocator : public fbs::Allocator {
266 public:
267 static constexpr size_t kAlignment = aos::kChannelDataAlignment;
268 AlignedVectorAllocator() {}
269 ~AlignedVectorAllocator();
270
271 std::optional<std::span<uint8_t>> Allocate(size_t size, size_t alignment,
272 fbs::SetZero set_zero) override;
273
274 std::optional<std::span<uint8_t>> InsertBytes(void *insertion_point,
275 size_t bytes, size_t alignment,
276 fbs::SetZero set_zero) override;
277
278 std::span<uint8_t> RemoveBytes(std::span<uint8_t> remove_bytes) override;
279
280 void Deallocate(std::span<uint8_t>) override;
281
282 aos::SharedSpan Release();
283
284 private:
285 struct SharedSpanHolder {
286 aos::AllocatorResizeableBuffer<aos::AlignedReallocator<kAlignment>> buffer;
287 absl::Span<const uint8_t> span;
288 };
289 uint8_t *data() { return buffer_.data() + buffer_.size() - allocated_size_; }
290
291 aos::AllocatorResizeableBuffer<aos::AlignedReallocator<kAlignment>> buffer_;
292
293 size_t allocated_size_ = 0u;
294 bool released_ = false;
295};
296
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800297// Allocates and owns a fixed-size memory buffer on the stack.
298//
299// This provides a convenient Allocator for use with the aos::fbs::Builder
300// in realtime code instead of trying to use the VectorAllocator.
Austin Schuh02e0d772024-05-30 16:41:06 -0700301template <std::size_t N, std::size_t alignment = 64>
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800302class FixedStackAllocator : public SpanAllocator {
303 public:
304 FixedStackAllocator() : SpanAllocator({buffer_, sizeof(buffer_)}) {}
305
306 private:
Austin Schuh02e0d772024-05-30 16:41:06 -0700307 alignas(alignment) uint8_t buffer_[N];
Maxwell Hendersonfb1e3bc2024-02-04 13:55:22 -0800308};
309
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700310namespace internal {
311std::ostream &DebugBytes(std::span<const uint8_t> span, std::ostream &os);
312inline void ClearSpan(std::span<uint8_t> span) {
313 memset(span.data(), 0, span.size());
314}
315// std::span::subspan does not do bounds checking.
316template <typename T>
317inline std::span<T> GetSubSpan(std::span<T> span, size_t offset,
318 size_t count = std::dynamic_extent) {
319 if (count != std::dynamic_extent) {
320 CHECK_LE(offset + count, span.size());
321 }
322 return span.subspan(offset, count);
323}
324// Normal users should never move any of the special flatbuffer types that we
325// provide. However, they do need to be moveable in order to support the use of
326// resizeable vectors. As such, we make all the move constructors private and
327// friend the TableMover struct. The TableMover struct is then used in places
328// that need to have moveable objects. It should never be used by a user.
329template <typename T>
330struct TableMover {
331 TableMover(std::span<uint8_t> buffer, ResizeableObject *parent)
332 : t(buffer, parent) {}
333 TableMover(std::span<uint8_t> buffer, Allocator *allocator)
334 : t(buffer, allocator) {}
335 TableMover(std::span<uint8_t> buffer, ::std::unique_ptr<Allocator> allocator)
336 : t(buffer, ::std::move(allocator)) {}
337 TableMover(TableMover &&) = default;
338 T t;
339};
340} // namespace internal
Austin Schuh8a399de2024-06-05 10:46:23 -0700341} // namespace aos::fbs
Austin Schuh02e0d772024-05-30 16:41:06 -0700342
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700343#endif // AOS_FLATBUFFERS_BASE_H_