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