blob: 893f5b4bcc02273a8d3be74c9b14572f3d72f9eb [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2026 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "upb_generator/minitable/layout_comment.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "google/protobuf/descriptor.h"
#include "upb/base/descriptor_constants.h"
#include "upb/mini_table/field.h"
#include "upb/mini_table/internal/field.h"
#include "upb/mini_table/internal/message.h"
#include "upb/mini_table/message.h"
#include "upb/reflection/def.hpp"
#include "upb_generator/file_layout.h"
// Must be last.
#include "upb/port/def.inc"
namespace upb {
namespace generator {
namespace {
// Every upb_Message begins with an 8-byte header (a tagged pointer to the
// upb_Message_Internal, padded to 8 bytes on 32-bit platforms). The hasbits
// that would overlap the header are reserved and never assigned.
constexpr uint32_t kHeaderSize = 8;
constexpr absl::string_view kPadding = "(padding)";
enum class Platform { k32Bit, k64Bit };
// One contiguous range of bytes within a message.
struct Region {
Region(uint32_t offset, uint32_t size, std::string field, std::string type,
std::string storage, std::string presence)
: offset(offset),
size(size),
field(std::move(field)),
type(std::move(type)),
storage(std::move(storage)),
presence(std::move(presence)) {}
uint32_t offset;
uint32_t size;
std::string field;
std::string type;
std::string storage;
std::string presence;
// For oneof data slots: one entry per member, all sharing this offset.
std::vector<Region> members;
};
const upb_MiniTable* GetMiniTable(const DefPoolPair& pools,
upb::MessageDefPtr message,
Platform platform) {
return platform == Platform::k64Bit ? pools.GetMiniTable64(message)
: pools.GetMiniTable32(message);
}
const upb_MiniTableField* GetField(const DefPoolPair& pools,
upb::FieldDefPtr field, Platform platform) {
return platform == Platform::k64Bit ? pools.GetField64(field)
: pools.GetField32(field);
}
uint32_t SizeOfRep(upb_FieldRep rep, Platform platform) {
switch (rep) {
case kUpb_FieldRep_1Byte:
return 1;
case kUpb_FieldRep_4Byte:
return 4;
case kUpb_FieldRep_StringView:
return platform == Platform::k64Bit ? 16 : 8;
case kUpb_FieldRep_8Byte:
return 8;
}
ABSL_CHECK(false) << "Unknown rep: " << rep;
return 0;
}
// Returns the .proto spelling of the field's type, e.g. "int32" or
// "foo.Bar".
std::string FieldTypeName(upb::FieldDefPtr field) {
switch (field.type()) {
case kUpb_FieldType_Group:
return absl::StrCat("group ", field.message_type().full_name());
case kUpb_FieldType_Message:
return field.message_type().full_name();
case kUpb_FieldType_Enum:
return absl::StrCat("enum ", field.enum_subdef().full_name(),
field.enum_subdef().is_closed() ? " (closed)" : "");
default:
// upb_FieldType values are identical to descriptor.proto's
// FieldDescriptorProto.Type values.
return std::string(google::protobuf::FieldDescriptor::TypeName(
static_cast<google::protobuf::FieldDescriptor::Type>(field.type())));
}
}
std::string FieldDisplayType(upb::FieldDefPtr field) {
if (field.IsMap()) {
upb::MessageDefPtr entry = field.message_type();
return absl::StrCat("map<", FieldTypeName(entry.map_key()), ", ",
FieldTypeName(entry.map_value()), ">");
}
if (field.IsSequence()) {
return absl::StrCat("repeated ", FieldTypeName(field),
field.packed() ? " [packed]" : "");
}
return FieldTypeName(field);
}
std::string FieldStorage(upb::FieldDefPtr field) {
if (field.IsMap()) return "upb_Map*";
if (field.IsSequence()) return "upb_Array*";
switch (field.ctype()) {
case kUpb_CType_Bool:
return "bool";
case kUpb_CType_Float:
return "float";
case kUpb_CType_Int32:
case kUpb_CType_Enum:
return "int32_t";
case kUpb_CType_UInt32:
return "uint32_t";
case kUpb_CType_Double:
return "double";
case kUpb_CType_Int64:
return "int64_t";
case kUpb_CType_UInt64:
return "uint64_t";
case kUpb_CType_String:
case kUpb_CType_Bytes:
return "upb_StringView";
case kUpb_CType_Message:
return "upb_Message*";
}
return "?";
}
std::string FieldLabel(upb::FieldDefPtr field) {
return absl::StrCat(field.name(), " = ", field.number());
}
// Computes all of the regions (fields, hasbits, oneof cases, padding) for a
// message on the given platform, sorted by offset.
std::vector<Region> ComputeRegions(const DefPoolPair& pools,
upb::MessageDefPtr message,
Platform platform) {
const uint32_t size =
GetMiniTable(pools, message, platform)->UPB_PRIVATE(size);
std::vector<Region> regions;
regions.push_back(
{0, kHeaderSize, "(message header)", "", "upb_Message", ""});
// Fields outside of any real oneof each get their own slot.
int hasbit_count = 0;
uint32_t hasbit_end = 0;
for (int i = 0; i < message.field_count(); i++) {
upb::FieldDefPtr field = message.field(i);
if (field.real_containing_oneof()) continue;
const upb_MiniTableField* mt_f = GetField(pools, field, platform);
std::string presence;
if (UPB_PRIVATE(_upb_MiniTableField_HasHasbit)(mt_f)) {
const uint16_t byte = UPB_PRIVATE(_upb_MiniTableField_HasbitOffset)(mt_f);
hasbit_count++;
hasbit_end = std::max<uint32_t>(hasbit_end, byte + 1);
presence = absl::Substitute("$0hasbit $1 (byte $2, bit $3)",
field.is_required() ? "required, " : "",
mt_f->presence, byte, mt_f->presence % 8);
} else if (!field.IsSequence()) {
ABSL_CHECK(!field.has_presence());
presence = "implicit";
}
regions.push_back(
{UPB_PRIVATE(_upb_MiniTableField_Offset)(mt_f),
SizeOfRep(UPB_PRIVATE(_upb_MiniTableField_GetRep)(mt_f), platform),
FieldLabel(field), FieldDisplayType(field), FieldStorage(field),
presence});
}
if (hasbit_count > 0) {
ABSL_CHECK_GE(hasbit_end, kHeaderSize);
regions.push_back({kHeaderSize, hasbit_end - kHeaderSize,
absl::StrCat("(hasbits: ", hasbit_count, " used)"), "",
"uint8_t[]", ""});
}
// All members of a real oneof share a single data slot and a single case
// slot. upb orders real oneofs before synthetic (proto3 optional) ones.
for (int i = 0; i < message.real_oneof_count(); i++) {
upb::OneofDefPtr oneof = message.oneof(i);
const upb_MiniTableField* first = GetField(pools, oneof.field(0), platform);
const uint32_t offset = UPB_PRIVATE(_upb_MiniTableField_Offset)(first);
const uint32_t case_offset =
UPB_PRIVATE(_upb_MiniTableField_OneofOffset)(first);
std::vector<Region> members;
uint32_t slot_size = 0;
for (int j = 0; j < oneof.field_count(); j++) {
upb::FieldDefPtr member = oneof.field(j);
const upb_MiniTableField* mt_member = GetField(pools, member, platform);
ABSL_CHECK_EQ(UPB_PRIVATE(_upb_MiniTableField_Offset)(mt_member), offset);
const uint32_t member_size = SizeOfRep(
UPB_PRIVATE(_upb_MiniTableField_GetRep)(mt_member), platform);
slot_size = std::max(slot_size, member_size);
members.push_back({offset, member_size,
absl::StrCat(" - ", FieldLabel(member)),
FieldDisplayType(member), FieldStorage(member), ""});
}
regions.push_back({case_offset, 4,
absl::StrCat("(oneof case: ", oneof.name(), ")"),
"field number", "uint32_t", ""});
Region& slot = regions.emplace_back(
offset, slot_size, absl::StrCat("oneof ", oneof.name()), "", "union",
absl::StrCat("oneof case @", case_offset));
slot.members = std::move(members);
}
std::sort(
regions.begin(), regions.end(),
[](const Region& a, const Region& b) { return a.offset < b.offset; });
// Fill in any gaps (including trailing alignment) with padding.
std::vector<Region> with_padding;
uint32_t pos = 0;
for (Region& r : regions) {
ABSL_CHECK_GE(r.offset, pos) << "Overlapping regions in "
<< message.full_name() << " at " << r.field;
if (r.offset > pos) {
with_padding.push_back(
{pos, r.offset - pos, std::string(kPadding), "", "", ""});
}
pos = r.offset + r.size;
with_padding.push_back(std::move(r));
}
ABSL_CHECK_LE(pos, size);
if (pos < size) {
with_padding.push_back(
{pos, size - pos, std::string(kPadding), "", "", ""});
}
return with_padding;
}
// Renders a list of rows as an ASCII table, with every line prefixed by
// `prefix`. The first row is the header. Columns whose index is set in
// `right_align` are right-aligned.
std::string RenderTable(const std::vector<std::vector<std::string>>& rows,
const std::vector<bool>& right_align,
absl::string_view prefix) {
ABSL_CHECK(!rows.empty());
const size_t cols = rows[0].size();
std::vector<size_t> widths(cols, 0);
for (const auto& row : rows) {
for (size_t c = 0; c < cols; c++) {
widths[c] = std::max(widths[c], row[c].size());
}
}
std::string sep = absl::StrCat(prefix, "+");
for (size_t c = 0; c < cols; c++) {
absl::StrAppend(&sep, std::string(widths[c] + 2, '-'), "+");
}
absl::StrAppend(&sep, "\n");
auto render_row = [&](const std::vector<std::string>& row) {
std::string line = absl::StrCat(prefix, "|");
for (size_t c = 0; c < cols; c++) {
std::string pad(widths[c] - row[c].size(), ' ');
if (right_align[c]) {
absl::StrAppend(&line, " ", pad, row[c], " |");
} else {
absl::StrAppend(&line, " ", row[c], pad, " |");
}
}
absl::StrAppend(&line, "\n");
return line;
};
std::string out = sep;
absl::StrAppend(&out, render_row(rows[0]), sep);
for (size_t i = 1; i < rows.size(); i++) {
absl::StrAppend(&out, render_row(rows[i]));
}
absl::StrAppend(&out, sep);
return out;
}
// Renders the layout table for one platform, with every line prefixed by
// " * " so that it can be embedded in a C block comment.
std::string RenderPlatformLayout(const DefPoolPair& pools,
upb::MessageDefPtr message,
Platform platform) {
std::vector<Region> regions = ComputeRegions(pools, message, platform);
uint32_t padding = 0;
for (const Region& r : regions) {
if (r.field == kPadding) padding += r.size;
}
std::vector<std::vector<std::string>> rows;
rows.push_back({"Offset", "Size", "Field", "Type", "Storage", "Presence"});
for (const Region& r : regions) {
rows.push_back({absl::StrCat(r.offset), absl::StrCat(r.size), r.field,
r.type, r.storage, r.presence});
for (const Region& m : r.members) {
rows.push_back(
{"", absl::StrCat(m.size), m.field, m.type, m.storage, m.presence});
}
}
return absl::StrCat(
absl::Substitute(
" * $0: $1 bytes ($2 bytes padding)\n",
platform == Platform::k64Bit ? "64-bit" : "32-bit",
GetMiniTable(pools, message, platform)->UPB_PRIVATE(size), padding),
RenderTable(rows, {true, true, false, false, false, false}, " * "));
}
} // namespace
std::string MessageLayoutComment(const DefPoolPair& pools,
upb::MessageDefPtr message) {
std::string out =
absl::StrCat("/* ", message.full_name(), ": in-memory layout.\n");
if (message.extension_range_count() > 0) {
absl::StrAppend(&out,
" * Extensions are stored out-of-line in "
"upb_Message_Internal.\n");
}
absl::StrAppend(
&out, " *\n", RenderPlatformLayout(pools, message, Platform::k64Bit),
" *\n", RenderPlatformLayout(pools, message, Platform::k32Bit), " */\n");
return out;
}
} // namespace generator
} // namespace upb
#include "upb/port/undef.inc"