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