blob: c2e045411a7aa62e54eed400e655ffeec8c9c7c4 [file] [log] [blame]
James Kuszmaulf5eb4682023-09-22 17:16:59 -07001#include "aos/flatbuffers/static_flatbuffers.h"
2
Stephan Pleines6191f1d2024-05-30 20:44:45 -07003#include <stddef.h>
4#include <stdint.h>
5
6#include <algorithm>
7#include <map>
8#include <optional>
9#include <ostream>
10#include <utility>
11#include <vector>
12
13#include "absl/strings/numbers.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070014#include "absl/strings/str_cat.h"
15#include "absl/strings/str_format.h"
16#include "absl/strings/str_join.h"
17#include "absl/strings/str_replace.h"
Stephan Pleines6191f1d2024-05-30 20:44:45 -070018#include "flatbuffers/base.h"
19#include "flatbuffers/string.h"
20#include "flatbuffers/vector.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070021#include "glog/logging.h"
22
Stephan Pleines6191f1d2024-05-30 20:44:45 -070023#include "aos/flatbuffers/base.h"
James Kuszmaulf5eb4682023-09-22 17:16:59 -070024#include "aos/json_to_flatbuffer.h"
Stephan Pleines6191f1d2024-05-30 20:44:45 -070025
James Kuszmaulf5eb4682023-09-22 17:16:59 -070026namespace aos::fbs {
27namespace {
28// Represents a given field within a type with all of the data that we actually
29// care about.
30struct FieldData {
31 // Field name.
32 std::string name;
33 // Whether it is an inline data type (scalar/struct vs vector/table/string).
34 bool is_inline = true;
35 // Whether this is a struct or not.
36 bool is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -080037 // Whether this is a repeated type (vector or string).
38 bool is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -070039 // Full C++ type of this field.
40 std::string full_type = "";
41 // Full flatbuffer type for this field.
42 // Only specified for Tables.
43 std::optional<std::string> fbs_type = std::nullopt;
44 // Size of this field in the inline field data (i.e., size of the field for
45 // is_inline fields; 4 bytes for the offset for vectors/tables/strings).
46 size_t inline_size = 0u;
47 // Alignment of the inline data.
48 size_t inline_alignment = 0u;
49 // vtable offset of the field.
50 size_t vtable_offset = 0u;
51};
52
53const reflection::Object *GetObject(const reflection::Schema *schema,
54 const int index) {
55 return (index == -1) ? schema->root_table() : schema->objects()->Get(index);
56}
57
58// Returns the flatbuffer field attribute with the specified name, if available.
59std::optional<std::string_view> GetAttribute(const reflection::Field *field,
60 std::string_view attribute) {
61 if (!field->has_attributes()) {
62 return std::nullopt;
63 }
64 const reflection::KeyValue *kv =
65 field->attributes()->LookupByKey(attribute.data());
66 if (kv == nullptr) {
67 return std::nullopt;
68 }
69 return kv->value()->string_view();
70}
71
72// Returns the implied value of an attribute that specifies a length (i.e., 0 if
73// the attribute is not specified; the integer value otherwise).
74int64_t GetLengthAttributeOrZero(const reflection::Field *field,
75 std::string_view attribute) {
76 std::optional<std::string_view> str = GetAttribute(field, attribute);
77 if (!str.has_value()) {
78 return 0;
79 }
80 int64_t value;
81 CHECK(absl::SimpleAtoi(str.value(), &value))
82 << ": Field " << field->name()->string_view()
83 << " must specify a positive integer for the " << attribute
84 << " attribute. Got \"" << str.value() << "\".";
85 CHECK_LE(0, value) << ": Field " << field->name()->string_view()
86 << " must have a non-negative " << attribute << ".";
87 return value;
88}
89
90const std::string ScalarCppType(const reflection::BaseType type) {
91 switch (type) {
92 case reflection::BaseType::Bool:
93 return "bool";
94 case reflection::BaseType::Byte:
95 return "int8_t";
96 case reflection::BaseType::UByte:
97 return "uint8_t";
98 case reflection::BaseType::Short:
99 return "int16_t";
100 case reflection::BaseType::UShort:
101 return "uint16_t";
102 case reflection::BaseType::Int:
103 return "int32_t";
104 case reflection::BaseType::UInt:
105 return "uint32_t";
106 case reflection::BaseType::Long:
107 return "int64_t";
108 case reflection::BaseType::ULong:
109 return "uint64_t";
110 case reflection::BaseType::Float:
111 return "float";
112 case reflection::BaseType::Double:
113 return "double";
114 case reflection::BaseType::UType:
115 case reflection::BaseType::String:
116 case reflection::BaseType::Vector:
117 case reflection::BaseType::Obj:
118 case reflection::BaseType::None:
119 case reflection::BaseType::Union:
120 case reflection::BaseType::Array:
121 case reflection::BaseType::MaxBaseType:
122 LOG(FATAL) << ": Type " << reflection::EnumNameBaseType(type)
123 << " not a scalar.";
124 }
125 LOG(FATAL) << "Unreachable";
126}
127
128const std::string FlatbufferNameToCppName(const std::string_view input) {
129 return absl::StrReplaceAll(input, {{".", "::"}});
130}
131
132const std::string AosNameForRawFlatbuffer(const std::string_view base_name) {
133 return absl::StrCat(base_name, "Static");
134}
135
136const std::string IncludePathForFbs(
137 std::string_view fbs_file, std::string_view include_suffix = "static") {
James Kuszmaul1e57af92023-12-20 15:34:54 -0800138 // Special case for the reflection_generated.h, which is checked into the
139 // repo.
140 // Note that we *do* autogenerated the reflection_static.h but that because
141 // it uses a special import path, we end up overriding the include anyways
142 // (note that we could muck around with the paths on the bazel side to instead
143 // get a cc_library with the correct include paths specified, although it is
144 // not clear that that would be any simpler than the extra else-if).
145 if (fbs_file == "reflection/reflection.fbs") {
146 if (include_suffix == "generated") {
147 return "flatbuffers/reflection_generated.h";
148 } else if (include_suffix == "static") {
149 return "aos/flatbuffers/reflection/reflection_static.h";
150 } else {
151 LOG(FATAL) << "This should be unreachable.";
152 }
153 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700154 fbs_file.remove_suffix(4);
155 return absl::StrCat(fbs_file, "_", include_suffix, ".h");
156}
157
158std::string ScalarOrEnumType(const reflection::Schema *schema,
159 const reflection::BaseType type, int index) {
160 return (index < 0) ? ScalarCppType(type)
161 : FlatbufferNameToCppName(
162 schema->enums()->Get(index)->name()->string_view());
163}
164
165void PopulateTypeData(const reflection::Schema *schema,
166 const reflection::Field *field_fbs, FieldData *field) {
167 VLOG(1) << aos::FlatbufferToJson(field_fbs);
168 const reflection::Type *type = field_fbs->type();
169 field->inline_size = type->base_size();
170 field->inline_alignment = type->base_size();
171 switch (type->base_type()) {
172 case reflection::BaseType::Bool:
173 case reflection::BaseType::Byte:
174 case reflection::BaseType::UByte:
175 case reflection::BaseType::Short:
176 case reflection::BaseType::UShort:
177 case reflection::BaseType::Int:
178 case reflection::BaseType::UInt:
179 case reflection::BaseType::Long:
180 case reflection::BaseType::ULong:
181 case reflection::BaseType::Float:
182 case reflection::BaseType::Double:
183 // We have a scalar field, so things are relatively
184 // straightforwards.
185 field->is_inline = true;
186 field->is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -0800187 field->is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700188 field->full_type =
189 ScalarOrEnumType(schema, type->base_type(), type->index());
190 return;
191 case reflection::BaseType::String: {
192 field->is_inline = false;
193 field->is_struct = false;
James Kuszmaul6be41022023-12-20 11:55:28 -0800194 field->is_repeated = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700195 field->full_type =
196 absl::StrFormat("::aos::fbs::String<%d>",
197 GetLengthAttributeOrZero(field_fbs, "static_length"));
198 return;
199 }
200 case reflection::BaseType::Vector: {
201 // We need to extract the name of the elements of the vector.
202 std::string element_type;
203 bool elements_are_inline = true;
James Kuszmaul6be41022023-12-20 11:55:28 -0800204 field->is_repeated = true;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700205 if (type->base_type() == reflection::BaseType::Vector) {
206 switch (type->element()) {
207 case reflection::BaseType::Obj: {
208 const reflection::Object *element_object =
209 GetObject(schema, type->index());
210 element_type =
211 FlatbufferNameToCppName(element_object->name()->string_view());
212 elements_are_inline = element_object->is_struct();
213 if (!element_object->is_struct()) {
214 element_type = AosNameForRawFlatbuffer(element_type);
215 field->fbs_type = element_object->name()->string_view();
216 }
217 break;
218 }
219 case reflection::BaseType::String:
220 element_type =
221 absl::StrFormat("::aos::fbs::String<%d>",
222 GetLengthAttributeOrZero(
223 field_fbs, "static_vector_string_length"));
224 elements_are_inline = false;
225 break;
226 case reflection::BaseType::Vector:
227 LOG(FATAL) << "Vectors of vectors do not exist in flatbuffers.";
228 default:
229 element_type =
230 ScalarOrEnumType(schema, type->element(), type->index());
231 };
232 }
233 field->is_inline = false;
234 field->is_struct = false;
235 field->full_type =
236 absl::StrFormat("::aos::fbs::Vector<%s, %d, %s, %s>", element_type,
237 GetLengthAttributeOrZero(field_fbs, "static_length"),
238 elements_are_inline ? "true" : "false",
239 GetAttribute(field_fbs, "force_align").value_or("0"));
240 return;
241 }
242 case reflection::BaseType::Obj: {
243 const reflection::Object *object = GetObject(schema, type->index());
244 field->is_inline = object->is_struct();
245 field->is_struct = object->is_struct();
James Kuszmaul6be41022023-12-20 11:55:28 -0800246 field->is_repeated = false;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700247 const std::string flatbuffer_name =
248 FlatbufferNameToCppName(object->name()->string_view());
249 if (field->is_inline) {
250 field->full_type = flatbuffer_name;
251 field->inline_size = object->bytesize();
252 field->inline_alignment = object->minalign();
253 } else {
254 field->fbs_type = object->name()->string_view();
255 field->full_type = AosNameForRawFlatbuffer(flatbuffer_name);
256 }
257 return;
258 }
259 case reflection::BaseType::None:
260 case reflection::BaseType::UType:
261 case reflection::BaseType::Union:
262 case reflection::BaseType::Array:
263 case reflection::BaseType::MaxBaseType:
264 LOG(FATAL) << ": Type " << reflection::EnumNameBaseType(type->base_type())
265 << " not supported currently.";
266 };
267}
268
269std::string MakeMoveConstructor(std::string_view type_name) {
270 return absl::StrFormat(R"code(
271 // We need to provide a MoveConstructor to allow this table to be
272 // used inside of vectors, but we do not want it readily available to
273 // users. See TableMover for more details.
274 %s(%s &&) = default;
275 friend struct ::aos::fbs::internal::TableMover<%s>;
276 )code",
277 type_name, type_name, type_name);
278}
279
280std::string MakeConstructor(std::string_view type_name) {
281 const std::string constructor_body =
282 R"code(
283 CHECK_EQ(buffer.size(), kSize);
284 CHECK_EQ(0u, reinterpret_cast<size_t>(buffer.data()) % kAlign);
285 PopulateVtable();
286)code";
287 return absl::StrFormat(R"code(
288 // Constructors for creating a flatbuffer object.
289 // Users should typically use the Builder class to create these objects,
290 // in order to allow it to populate the root table offset.
291
292 // The buffer provided to these constructors should be aligned to kAlign
293 // and kSize in length.
294 // The parent/allocator may not be nullptr.
295 %s(std::span<uint8_t> buffer, ::aos::fbs::ResizeableObject *parent) : Table(buffer, parent) {
296 %s
297 }
298 %s(std::span<uint8_t> buffer, ::aos::fbs::Allocator *allocator) : Table(buffer, allocator) {
299 %s
300 }
301 %s(std::span<uint8_t> buffer, ::std::unique_ptr<::aos::fbs::Allocator> allocator) : Table(buffer, ::std::move(allocator)) {
302 %s
303 }
304)code",
305 type_name, constructor_body, type_name,
306 constructor_body, type_name, constructor_body);
307}
308
309std::string MemberName(const FieldData &data) {
310 return absl::StrCat(data.name, "_");
311}
312
313std::string ObjectAbsoluteOffsetName(const FieldData &data) {
314 return absl::StrCat("object_absolute_offset_", data.name);
315}
316
317std::string InlineAbsoluteOffsetName(const FieldData &data) {
318 return absl::StrCat("kInlineAbsoluteOffset_", data.name);
319}
320
321// Generate the clear_* method for the requested field.
322std::string MakeClearer(const FieldData &field) {
323 std::string logical_clearer;
324 if (!field.is_inline) {
325 logical_clearer = MemberName(field) + ".reset();";
326 }
327 return absl::StrFormat(R"code(
328 // Clears the %s field. This will cause has_%s() to return false.
329 void clear_%s() {
330 %s
331 ClearField(%s, %d, %d);
332 }
333 )code",
334 field.name, field.name, field.name, logical_clearer,
335 InlineAbsoluteOffsetName(field), field.inline_size,
336 field.vtable_offset);
337}
338
339// Generate the has_* method for the requested field.
340std::string MakeHaser(const FieldData &field) {
341 return absl::StrFormat(R"code(
342 // Returns true if the %s field is set and can be accessed.
343 bool has_%s() const {
344 return AsFlatbuffer().has_%s();
345 }
346 )code",
347 field.name, field.name, field.name);
348}
349
350// Generates the accessors for fields which are stored inline in the flatbuffer
351// table (scalars, structs, and enums) .
352std::string MakeInlineAccessors(const FieldData &field,
353 const size_t inline_absolute_offset) {
354 CHECK_EQ(inline_absolute_offset % field.inline_alignment, 0u)
355 << ": Unaligned field " << field.name << " on " << field.full_type
356 << " with inline offset of " << inline_absolute_offset
357 << " and alignment of " << field.inline_alignment;
358 const std::string setter =
359 absl::StrFormat(R"code(
360 // Sets the %s field, causing it to be populated if it is not already.
361 // This will populate the field even if the specified value is the default.
362 void set_%s(const %s &value) {
363 SetField<%s>(%s, %d, value);
364 }
365 )code",
366 field.name, field.name, field.full_type, field.full_type,
367 InlineAbsoluteOffsetName(field), field.vtable_offset);
368 const std::string getters = absl::StrFormat(
369 R"code(
370 // Returns the value of %s if set; nullopt otherwise.
371 std::optional<%s> %s() const {
372 return has_%s() ? std::make_optional(Get<%s>(%s)) : std::nullopt;;
373 }
374 // Returns a pointer to modify the %s field.
375 // The pointer may be invalidated by mutations/movements of the underlying buffer.
376 // Returns nullptr if the field is not set.
377 %s* mutable_%s() {
378 return has_%s() ? MutableGet<%s>(%s) : nullptr;
379 }
380 )code",
381 field.name, field.full_type, field.name, field.name, field.full_type,
382 InlineAbsoluteOffsetName(field), field.name, field.full_type, field.name,
383 field.name, field.full_type, InlineAbsoluteOffsetName(field));
384 const std::string clearer = MakeClearer(field);
385 return setter + getters + clearer + MakeHaser(field);
386}
387
388// Generates the accessors for fields which are not inline fields and have an
389// offset to the actual field content stored inline in the flatbuffer table.
390std::string MakeOffsetDataAccessors(const FieldData &field) {
391 const std::string setter = absl::StrFormat(
392 R"code(
393 // Creates an empty object for the %s field, which you can
394 // then populate/modify as desired.
395 // The field must not be populated yet.
396 %s* add_%s() {
397 CHECK(!%s.has_value());
398 constexpr size_t kVtableIndex = %d;
399 // Construct the *Static object that we will use for managing this subtable.
400 %s.emplace(BufferForObject(%s, %s::kSize, kAlign), this);
401 // Actually set the appropriate fields in the flatbuffer memory itself.
402 SetField<::flatbuffers::uoffset_t>(%s, kVtableIndex, %s + %s::kOffset - %s);
403 return &%s.value().t;
404 }
405 )code",
406 field.name, field.full_type, field.name, MemberName(field),
407 field.vtable_offset, MemberName(field), ObjectAbsoluteOffsetName(field),
408 field.full_type, InlineAbsoluteOffsetName(field),
409 ObjectAbsoluteOffsetName(field), field.full_type,
410 InlineAbsoluteOffsetName(field), MemberName(field));
411 const std::string getters = absl::StrFormat(
412 R"code(
413 // Returns a pointer to the %s field, if set. nullptr otherwise.
414 const %s* %s() const {
415 return %s.has_value() ? &%s.value().t : nullptr;
416 }
417 %s* mutable_%s() {
418 return %s.has_value() ? &%s.value().t : nullptr;
419 }
420 )code",
421 field.name, field.full_type, field.name, MemberName(field),
422 MemberName(field), field.full_type, field.name, MemberName(field),
423 MemberName(field));
424 return setter + getters + MakeClearer(field) + MakeHaser(field);
425}
426
427std::string MakeAccessors(const FieldData &field,
428 size_t inline_absolute_offset) {
429 return field.is_inline ? MakeInlineAccessors(field, inline_absolute_offset)
430 : MakeOffsetDataAccessors(field);
431}
432
433std::string MakeMembers(const FieldData &field,
434 std::string_view offset_data_absolute_offset,
435 size_t inline_absolute_offset) {
436 if (field.is_inline) {
437 return absl::StrFormat(
438 R"code(
439 // Offset from the start of the buffer to the inline data for the %s field.
440 static constexpr size_t %s = %d;
441 )code",
442 field.name, InlineAbsoluteOffsetName(field), inline_absolute_offset);
443 } else {
444 return absl::StrFormat(
445 R"code(
446 // Members relating to the %s field.
447 //
448 // *Static object used for managing this subtable. Will be nullopt
449 // when the field is not populated.
450 // We use the TableMover to be able to make this object moveable.
451 std::optional<::aos::fbs::internal::TableMover<%s>> %s;
452 // Offset from the start of the buffer to the start of the actual
453 // data for this field. Will be updated even when the table is not
454 // populated, so that we know where to construct it when requested.
455 size_t %s = %s;
456 // Offset from the start of the buffer to the offset in the inline data for
457 // this field.
458 static constexpr size_t %s = %d;
459 )code",
460 field.name, field.full_type, MemberName(field),
461 ObjectAbsoluteOffsetName(field), offset_data_absolute_offset,
462 InlineAbsoluteOffsetName(field), inline_absolute_offset);
463 }
464}
465
466std::string MakeFullClearer(const std::vector<FieldData> &fields) {
467 std::vector<std::string> clearers;
468 for (const FieldData &field : fields) {
469 clearers.emplace_back(absl::StrFormat("clear_%s();", field.name));
470 }
471 return absl::StrFormat(R"code(
472 // Clears every field of the table, removing any existing state.
473 void Clear() { %s })code",
474 absl::StrJoin(clearers, "\n"));
475}
476
James Kuszmaul6be41022023-12-20 11:55:28 -0800477// Creates the FromFlatbuffer() method that copies from a flatbuffer object API
478// object (i.e., the FlatbufferT types).
479std::string MakeObjectCopier(const std::vector<FieldData> &fields) {
480 std::vector<std::string> copiers;
481 for (const FieldData &field : fields) {
482 if (field.is_struct) {
483 // Structs are stored as unique_ptr<FooStruct>
484 copiers.emplace_back(absl::StrFormat(R"code(
485 if (other.%s) {
486 set_%s(*other.%s);
487 }
488 )code",
489 field.name, field.name, field.name));
490 } else if (field.is_inline) {
491 // Inline non-struct elements are stored as FooType.
492 copiers.emplace_back(absl::StrFormat(R"code(
493 set_%s(other.%s);
494 )code",
495 field.name, field.name));
496 } else if (field.is_repeated) {
497 // strings are stored as std::string's.
498 // vectors are stored as std::vector's.
499 copiers.emplace_back(absl::StrFormat(R"code(
500 // Unconditionally copy strings/vectors, even if it will just end up
501 // being 0-length (this maintains consistency with the flatbuffer Pack()
502 // behavior).
503 if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(other.%s)) {
504 // Fail if we were unable to copy (e.g., if we tried to copy in a long
505 // vector and do not have the space for it).
506 return false;
507 }
508 )code",
509 field.name, field.name));
510 } else {
511 // Tables are stored as unique_ptr<FooTable>
512 copiers.emplace_back(absl::StrFormat(R"code(
513 if (other.%s) {
514 if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(*other.%s)) {
515 // Fail if we were unable to copy (e.g., if we tried to copy in a long
516 // vector and do not have the space for it).
517 return false;
518 }
519 }
520 )code",
521 field.name, field.name, field.name));
522 }
523 }
524 return absl::StrFormat(
525 R"code(
526 // Copies the contents of the provided flatbuffer into this flatbuffer,
527 // returning true on success.
528 // Because the Flatbuffer Object API does not provide any concept of an
529 // optionally populated scalar field, all scalar fields will be populated
530 // after a call to FromFlatbufferObject().
531 // This is a deep copy, and will call FromFlatbufferObject on
532 // any constituent objects.
533 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
534 Clear();
535 %s
536 return true;
537 }
538 [[nodiscard]] bool FromFlatbuffer(const flatbuffers::unique_ptr<Flatbuffer::NativeTableType>& other) {
539 return FromFlatbuffer(*other);
540 }
541)code",
542 absl::StrJoin(copiers, "\n"));
543}
544
545// Creates the FromFlatbuffer() method that copies from an actual flatbuffer
546// object.
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700547std::string MakeCopier(const std::vector<FieldData> &fields) {
548 std::vector<std::string> copiers;
549 for (const FieldData &field : fields) {
550 if (field.is_struct) {
551 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800552 if (other.has_%s()) {
553 set_%s(*other.%s());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700554 }
555 )code",
556 field.name, field.name, field.name));
557 } else if (field.is_inline) {
558 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800559 if (other.has_%s()) {
560 set_%s(other.%s());
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700561 }
562 )code",
563 field.name, field.name, field.name));
564 } else {
565 copiers.emplace_back(absl::StrFormat(R"code(
James Kuszmaul692780f2023-12-20 14:01:56 -0800566 if (other.has_%s()) {
567 if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(other.%s())) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700568 // Fail if we were unable to copy (e.g., if we tried to copy in a long
569 // vector and do not have the space for it).
570 return false;
571 }
572 }
573 )code",
574 field.name, field.name, field.name));
575 }
576 }
577 return absl::StrFormat(
578 R"code(
579 // Copies the contents of the provided flatbuffer into this flatbuffer,
580 // returning true on success.
James Kuszmaul710883b2023-12-14 14:34:48 -0800581 // This is a deep copy, and will call FromFlatbuffer on any constituent
582 // objects.
James Kuszmaul692780f2023-12-20 14:01:56 -0800583 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700584 Clear();
585 %s
586 return true;
587 }
James Kuszmaul692780f2023-12-20 14:01:56 -0800588 // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
589 // to ease implementation of the aos::fbs::Vector internals.
590 [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
591 return FromFlatbuffer(*CHECK_NOTNULL(other));
592 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700593)code",
594 absl::StrJoin(copiers, "\n"));
595}
596
597std::string MakeSubObjectList(const std::vector<FieldData> &fields) {
598 size_t num_object_fields = 0;
599 std::vector<std::string> object_offsets;
600 std::vector<std::string> objects;
601 std::vector<std::string> inline_offsets;
602 for (const FieldData &field : fields) {
603 if (!field.is_inline) {
604 ++num_object_fields;
605 object_offsets.push_back(
606 absl::StrFormat("&%s", ObjectAbsoluteOffsetName(field)));
607 objects.push_back(absl::StrFormat("&%s->t", MemberName(field)));
608 inline_offsets.push_back(InlineAbsoluteOffsetName(field));
609 }
610 }
611 if (num_object_fields == 0) {
612 return R"code(
613 // This object has no non-inline subobjects, so we don't have to do anything special.
614 size_t NumberOfSubObjects() const final { return 0; }
615 using ::aos::fbs::ResizeableObject::SubObject;
616 SubObject GetSubObject(size_t) final { LOG(FATAL) << "No subobjects."; }
617 )code";
618 }
619 return absl::StrFormat(R"code(
620 size_t NumberOfSubObjects() const final { return %d; }
621 using ::aos::fbs::ResizeableObject::SubObject;
622 SubObject GetSubObject(size_t index) final {
623 SubObject object;
624 // Note: The below arrays are local variables rather than class members to
625 // avoid having to deal with what happens to them if the object is moved.
626
627 // Array of the members that we use for tracking where the buffers for
628 // each subobject belong.
629 // Pointers because these may need to be modified when memory is
630 // inserted into the buffer.
631 const std::array<size_t*, %d> subobject_object_offsets{%s};
632 // Actual subobjects; note that the pointers will be invalid when the
633 // field is not populated.
634 const std::array<::aos::fbs::ResizeableObject*, %d> subobject_objects{%s};
635 // Absolute offsets from the start of the buffer to where the inline
636 // entry is for each table. These offsets do not need to change at
637 // runtime (because memory is never inserted into the start of
638 // a given table), but the offsets pointed to by these offsets
639 // may need to be updated.
640 const std::array<size_t, %d> subobject_inline_offsets{%s};
641 object.inline_entry = MutableGet<::flatbuffers::uoffset_t>(subobject_inline_offsets[index]);
642 object.object = (*object.inline_entry == 0) ? nullptr : subobject_objects[index];
643 object.absolute_offset = subobject_object_offsets[index];
644 return object;
645 }
646 )code",
647 num_object_fields, num_object_fields,
648 absl::StrJoin(object_offsets, ", "), num_object_fields,
649 absl::StrJoin(objects, ", "), num_object_fields,
650 absl::StrJoin(inline_offsets, ", "));
651}
652
653std::string AlignCppString(const std::string_view expression,
654 const std::string_view alignment) {
655 return absl::StrFormat("::aos::fbs::PaddedSize(%s, %s)", expression,
656 alignment);
657}
658
659std::string MakeInclude(std::string_view path, bool system = false) {
660 return absl::StrFormat("#include %s%s%s\n", system ? "<" : "\"", path,
661 system ? ">" : "\"");
662}
663
664} // namespace
665GeneratedObject GenerateCodeForObject(const reflection::Schema *schema,
666 int object_index) {
667 return GenerateCodeForObject(schema, GetObject(schema, object_index));
668}
669GeneratedObject GenerateCodeForObject(const reflection::Schema *schema,
670 const reflection::Object *object) {
671 std::vector<FieldData> fields;
672 for (const reflection::Field *field_fbs : *object->fields()) {
673 if (field_fbs->deprecated()) {
674 // Don't codegen anything for deprecated fields.
675 continue;
676 }
677 FieldData field{.name = field_fbs->name()->str(),
678 .vtable_offset = field_fbs->offset()};
679 PopulateTypeData(schema, field_fbs, &field);
680 fields.push_back(field);
681 }
682 const size_t nominal_min_align = object->minalign();
683 std::string out_of_line_member_size = "";
684 // inline_absolute_offset tracks the current position of the inline table
685 // contents so that we can assign static offsets to each field.
686 size_t inline_absolute_offset = sizeof(soffset_t);
687 // offset_data_relative_offset tracks the current size of the various
688 // sub-tables/vectors/strings that get stored at the end of the buffer.
689 // For simplicity, the offset data will start at a fully aligned offset
690 // (which may be larger than the soffset_t at the start of the table).
691 // Note that this is a string because it's irritating to actually pipe the
692 // numbers for size/alignment up here, so we just accumulate them here and
693 // then write the expression directly into the C++.
694 std::string offset_data_relative_offset = "0";
695 const std::string offset_data_start_expression =
696 "::aos::fbs::PaddedSize(kVtableStart + kVtableSize, kAlign)";
697 std::string accessors;
698 std::string members;
699 std::set<std::string> includes = {
700 MakeInclude("optional", true),
701 MakeInclude("aos/flatbuffers/static_table.h"),
702 MakeInclude("aos/flatbuffers/static_vector.h")};
703 for (const reflection::SchemaFile *file : *schema->fbs_files()) {
704 includes.insert(
705 MakeInclude(IncludePathForFbs(file->filename()->string_view())));
706 includes.insert(MakeInclude(
707 IncludePathForFbs(file->filename()->string_view(), "generated")));
708 for (const flatbuffers::String *included : *file->included_filenames()) {
709 includes.insert(MakeInclude(IncludePathForFbs(included->string_view())));
710 }
711 }
712 std::vector<std::string> alignments;
713 std::set<std::string> subobject_names;
714 for (const FieldData &field : fields) {
715 inline_absolute_offset =
716 PaddedSize(inline_absolute_offset, field.inline_alignment);
717 if (!field.is_inline) {
718 // All sub-fields will get aligned to the parent alignment. This makes
719 // some book-keeping a bit easier, at the expense of some gratuitous
720 // padding.
721 offset_data_relative_offset =
722 AlignCppString(offset_data_relative_offset, "kAlign");
723 alignments.push_back(field.full_type + "::kAlign");
724 } else {
725 alignments.push_back(std::to_string(field.inline_alignment));
726 }
727 const std::string offset_data_absolute_offset =
728 offset_data_start_expression + " + " + offset_data_relative_offset;
729 accessors += MakeAccessors(field, inline_absolute_offset);
730 members +=
731 MakeMembers(field, offset_data_absolute_offset, inline_absolute_offset);
732
733 inline_absolute_offset += field.inline_size;
734 if (!field.is_inline) {
735 offset_data_relative_offset +=
736 absl::StrFormat(" + %s::kSize", field.full_type);
737 }
738 if (field.fbs_type.has_value()) {
739 // Is this not getting populate for the root schema?
740 subobject_names.insert(field.fbs_type.value());
741 }
742 }
743
James Kuszmaula75cd7c2023-12-07 15:52:51 -0800744 const std::string alignment = absl::StrCat(
745 "static constexpr size_t kAlign = std::max<size_t>({kMinAlign, ",
746 absl::StrJoin(alignments, ", "), "});\n");
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700747 const std::string size =
748 absl::StrCat("static constexpr size_t kSize = ",
749 AlignCppString(offset_data_start_expression + " + " +
750 offset_data_relative_offset,
751 "kAlign"),
752 ";");
753 const size_t inline_data_size = inline_absolute_offset;
754 const std::string constants = absl::StrFormat(
755 R"code(
756 // Space taken up by the inline portion of the flatbuffer table data, in bytes.
757 static constexpr size_t kInlineDataSize = %d;
758 // Space taken up by the vtable for this object, in bytes.
759 static constexpr size_t kVtableSize = sizeof(::flatbuffers::voffset_t) * (2 + %d);
760 // Offset from the start of the internal memory buffer to the start of the vtable.
761 static constexpr size_t kVtableStart = ::aos::fbs::PaddedSize(kInlineDataSize, alignof(::flatbuffers::voffset_t));
762 // Required alignment of this object. The buffer that this object gets constructed
763 // into must be aligned to this value.
764 %s
765 // Nominal size of this object, in bytes. The object may grow beyond this size,
766 // but will always start at this size and so the initial buffer must match
767 // this size.
768 %s
769 static_assert(%d <= kAlign, "Flatbuffer schema minalign should not exceed our required alignment.");
770 // Offset from the start of the memory buffer to the start of any out-of-line data (subtables,
771 // vectors, strings).
772 static constexpr size_t kOffsetDataStart = %s;
773 // Size required for a buffer that includes a root table offset at the start.
774 static constexpr size_t kRootSize = ::aos::fbs::PaddedSize(kSize + sizeof(::flatbuffers::uoffset_t), kAlign);
775 // Minimum size required to build this flatbuffer in an entirely unaligned buffer
776 // (including the root table offset). Made to be a multiple of kAlign for convenience.
777 static constexpr size_t kUnalignedBufferSize = kRootSize + kAlign;
778 // Offset at which the table vtable offset occurs. This is only needed for vectors.
779 static constexpr size_t kOffset = 0;
780 // Various overrides to support the Table parent class.
781 size_t FixedVtableOffset() const final { return kVtableStart; }
782 size_t VtableSize() const final { return kVtableSize; }
783 size_t InlineTableSize() const final { return kInlineDataSize; }
784 size_t OffsetDataStart() const final { return kOffsetDataStart; }
785 size_t Alignment() const final { return kAlign; }
786 // Exposes the name of the flatbuffer type to allow interchangeable use
787 // of the Flatbuffer and FlatbufferStatic types in various AOS methods.
788 static const char *GetFullyQualifiedName() { return Flatbuffer::GetFullyQualifiedName(); }
789)code",
790 inline_data_size, object->fields()->size(), alignment, size,
791 nominal_min_align, offset_data_start_expression);
792 const std::string_view fbs_type_name = object->name()->string_view();
793 const std::string type_namespace = FlatbufferNameToCppName(
794 fbs_type_name.substr(0, fbs_type_name.find_last_of(".")));
795 const std::string type_name = AosNameForRawFlatbuffer(
796 fbs_type_name.substr(fbs_type_name.find_last_of(".") + 1));
797 const std::string object_code = absl::StrFormat(
798 R"code(
799namespace %s {
800class %s : public ::aos::fbs::Table {
801 public:
802 // The underlying "raw" flatbuffer type for this type.
803 typedef %s Flatbuffer;
James Kuszmaul6be41022023-12-20 11:55:28 -0800804 typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType> FlatbufferObjectType;
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700805 // Returns this object as a flatbuffer type. This reference may not be valid
806 // following mutations to the underlying flatbuffer, due to how memory may get
807 // may get moved around.
808 const Flatbuffer &AsFlatbuffer() const { return *GetFlatbuffer<Flatbuffer>(); }
809%s
810%s
811 virtual ~%s() {}
812%s
813%s
814%s
James Kuszmaul6be41022023-12-20 11:55:28 -0800815%s
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700816 private:
817%s
818%s
819%s
820};
821}
822 )code",
823 type_namespace, type_name, FlatbufferNameToCppName(fbs_type_name),
824 constants, MakeConstructor(type_name), type_name, accessors,
James Kuszmaul6be41022023-12-20 11:55:28 -0800825 MakeFullClearer(fields), MakeCopier(fields), MakeObjectCopier(fields),
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700826 MakeMoveConstructor(type_name), members, MakeSubObjectList(fields));
827
828 GeneratedObject result;
829 result.name = fbs_type_name;
830 result.include_declarations = includes;
831 result.code = object_code;
832 result.subobjects = subobject_names;
833 return result;
834}
835
836namespace {
837
838// Generated C++ code for an entire fbs file.
839// This includes all of the actual C++ code that will be written to a file (call
840// GenerateCode() to actually get the desired contents of the file).
841struct GeneratedCode {
842 // Prefix (for include guards).
843 std::string contents_prefix;
844 // Full set of required #include declarations.
845 std::set<std::string> include_declarations;
846 // Ordered list of objects (order is necessary to ensure that any dependencies
847 // between objects are managed correctly).
848 std::vector<GeneratedObject> objects;
849 // Suffix (for include guards).
850 std::string contents_suffix;
851
852 // Combine the above things into the string that actually needs to be written
853 // to a file.
854 std::string GenerateCode() const;
855 // Combines the code for multiple objects into one.
856 static GeneratedCode MergeCode(const std::vector<GeneratedObject> &objects);
857};
858
859std::string GeneratedCode::GenerateCode() const {
860 std::string result =
861 contents_prefix + absl::StrJoin(include_declarations, "");
862 for (const auto &object : objects) {
863 result += object.code;
864 }
865 result += contents_suffix;
866 return result;
867}
868
869GeneratedCode GeneratedCode::MergeCode(
870 const std::vector<GeneratedObject> &objects) {
871 GeneratedCode result;
872 // TODO(james): Should we use #ifdef include guards instead?
873 result.contents_prefix =
874 "#pragma once\n// This is a generated file. Do not modify.\n";
875 // We need to get the ordering of objects correct in order to ensure that
876 // depended-on objects appear before their dependees.
877 // In order to do this, we:
878 // 1) Assume that any objects not in the provided vector must exist in
879 // #includes and so can be ignored.
880 // 2) Create a list of all the objects we have been provided but which we have
881 // not yet added to the results vector.
882 // 3) Until said list is empty, we iterate over it and find any object(s)
883 // which have no dependencies in the list itself, and add them to the
884 // result.
885 // We aren't going to worry about efficient graph traversal here or anything.
886 // We also don't currently attempt to support circular dependencies.
887 std::map<std::string_view, const GeneratedObject *> remaining_objects;
888 for (const auto &object : objects) {
889 remaining_objects[object.name] = &object;
890 }
891 while (!remaining_objects.empty()) {
892 std::string_view to_remove;
893 for (const auto &pair : remaining_objects) {
894 bool has_dependencies = false;
895 for (const std::string_view subobject : pair.second->subobjects) {
896 if (remaining_objects.contains(subobject)) {
897 has_dependencies = true;
898 }
899 }
900 if (has_dependencies) {
901 continue;
902 }
903 to_remove = pair.first;
904 result.objects.push_back(*pair.second);
905 result.include_declarations.insert(
906 pair.second->include_declarations.begin(),
907 pair.second->include_declarations.end());
908 break;
909 }
910 // In order to support circular dependencies, two main things have to
911 // change:
912 // 1. We have to dynamically allow depopulating table members (rather than
913 // just supporting dynamically lengthed vectors).
914 // 2. Some of the codegen needs to be tweaked so that we can have the
915 // generated
916 // C++ classes depend on one another.
917 CHECK(!to_remove.empty())
918 << ": Circular dependencies in flatbuffers schemas are not supported.";
919 CHECK_EQ(1u, remaining_objects.erase(to_remove))
920 << ": Failed to remove " << to_remove;
921 }
922 return result;
923}
924} // namespace
925
James Kuszmaul9a2d5f02023-12-14 11:38:35 -0800926std::string GenerateCodeForRootTableFile(const reflection::Schema *schema,
927 std::string_view file_hint) {
928 const reflection::Object *root_object = GetObject(schema, -1);
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700929 const std::string_view root_file =
James Kuszmaul9a2d5f02023-12-14 11:38:35 -0800930 (root_object == nullptr) ? file_hint
931 : root_object->declaration_file()->string_view();
932 std::vector<GeneratedObject> objects;
933 if (root_object != nullptr) {
934 objects.push_back(GenerateCodeForObject(schema, root_object));
935 }
James Kuszmaulf5eb4682023-09-22 17:16:59 -0700936 for (const reflection::Object *object : *schema->objects()) {
937 if (object->is_struct()) {
938 continue;
939 }
940 if (object->declaration_file()->string_view() == root_file) {
941 objects.push_back(GenerateCodeForObject(schema, object));
942 }
943 }
944 return GeneratedCode::MergeCode(objects).GenerateCode();
945}
946} // namespace aos::fbs