Add 8-bit and 16-bit enum support to TcTable parsing and Reflection This change introduces Protobuf C++ runtime primitives for singular 8-bit and 16-bit closed enum fields (repeated enums remain standard 32-bit RepeatedField<int>): - Added kRep16Bits and singular 8-bit/16-bit enum FieldType definitions to generated_message_tctable_impl.h. - Implemented fast-path and miniparse routines for singular 8-bit and 16-bit enums in generated_message_tctable_lite.cc without memory overwrites. - In generated_message_tctable_gen.cc, restricted 8-bit and 16-bit enum type cards strictly to singular fields, ensuring repeated enum fields always use standard 32-bit routines. - Added 8-bit and 16-bit offset tagging support in generated_message_reflection.h and updated GetEnumValue/SetEnumValueInternal in generated_message_reflection.cc. - Added comprehensive unit tests in generated_message_tctable_lite_test.cc and generated_message_reflection_unittest.cc. PiperOrigin-RevId: 970007510
diff --git a/src/google/protobuf/generated_message_reflection.cc b/src/google/protobuf/generated_message_reflection.cc index 492c210..a108e12 100644 --- a/src/google/protobuf/generated_message_reflection.cc +++ b/src/google/protobuf/generated_message_reflection.cc
@@ -115,22 +115,6 @@ } // anonymous namespace namespace internal { -ReflectionSchema::ReflectionSchema(const Message* default_instance, - const uint32_t* offsets, - const uint32_t* has_bit_indices, - int has_bits_offset, int extensions_offset, - int oneof_case_offset, int object_size, - int split_offset, int sizeof_split) - : default_instance_(default_instance), - offsets_(offsets), - has_bit_indices_(has_bit_indices), - has_bits_offset_(has_bits_offset), - extensions_offset_(extensions_offset), - oneof_case_offset_(oneof_case_offset), - object_size_(object_size), - split_offset_(split_offset), - sizeof_split_(sizeof_split) {} - ReflectionSchema ReflectionSchema::MigrationToReflectionSchema( const MessageGlobalsBase* const* message_globals, const uint32_t* offsets, MigrationSchema migration_schema) { @@ -993,8 +977,19 @@ SWAP_VALUES(FLOAT, float); SWAP_VALUES(DOUBLE, double); SWAP_VALUES(BOOL, bool); - SWAP_VALUES(ENUM, int); #undef SWAP_VALUES + case FieldDescriptor::CPPTYPE_ENUM: + if (r->schema_.IsEnum8(field)) { + std::swap(*r->MutableRaw<uint8_t>(lhs, field), + *r->MutableRaw<uint8_t>(rhs, field)); + } else if (r->schema_.IsEnum16(field)) { + std::swap(*r->MutableRaw<uint16_t>(lhs, field), + *r->MutableRaw<uint16_t>(rhs, field)); + } else { + std::swap(*r->MutableRaw<int>(lhs, field), + *r->MutableRaw<int>(rhs, field)); + } + break; default: ABSL_LOG(FATAL) << "Unimplemented type: " << field->cpp_type(); } @@ -1617,8 +1612,26 @@ #undef CLEAR_TYPE case FieldDescriptor::CPPTYPE_ENUM: - *MutableRaw<int>(message, field) = - field->default_value_enum()->number(); + if (schema_.IsEnum8(field)) { + if (schema_.IsEnumSigned(field)) { + *MutableRaw<int8_t>(message, field) = + static_cast<int8_t>(field->default_value_enum()->number()); + } else { + *MutableRaw<uint8_t>(message, field) = + static_cast<uint8_t>(field->default_value_enum()->number()); + } + } else if (schema_.IsEnum16(field)) { + if (schema_.IsEnumSigned(field)) { + *MutableRaw<int16_t>(message, field) = + static_cast<int16_t>(field->default_value_enum()->number()); + } else { + *MutableRaw<uint16_t>(message, field) = + static_cast<uint16_t>(field->default_value_enum()->number()); + } + } else { + *MutableRaw<int>(message, field) = + field->default_value_enum()->number(); + } break; case FieldDescriptor::CPPTYPE_STRING: { @@ -2433,6 +2446,18 @@ field->number(), field->default_value_enum()->number()); } else if (schema_.InRealOneof(field) && !HasOneofField(message, field)) { value = field->default_value_enum()->number(); + } else if (schema_.IsEnum8(field)) { + if (schema_.IsEnumSigned(field)) { + value = GetField<int8_t>(message, field); + } else { + value = GetField<uint8_t>(message, field); + } + } else if (schema_.IsEnum16(field)) { + if (schema_.IsEnumSigned(field)) { + value = GetField<int16_t>(message, field); + } else { + value = GetField<uint16_t>(message, field); + } } else { value = GetField<int>(message, field); } @@ -2468,6 +2493,18 @@ if (field->is_extension()) { MutableExtensionSet(message)->Set<int>(message->GetArena(), field->number(), field->type(), value, field); + } else if (schema_.IsEnum8(field)) { + if (schema_.IsEnumSigned(field)) { + SetField<int8_t>(message, field, static_cast<int8_t>(value)); + } else { + SetField<uint8_t>(message, field, static_cast<uint8_t>(value)); + } + } else if (schema_.IsEnum16(field)) { + if (schema_.IsEnumSigned(field)) { + SetField<int16_t>(message, field, static_cast<int16_t>(value)); + } else { + SetField<uint16_t>(message, field, static_cast<uint16_t>(value)); + } } else { SetField<int>(message, field, value); } @@ -3245,6 +3282,11 @@ "Code assumes uint64_t and double are the same size."); return absl::bit_cast<uint64_t>(GetRaw<double>(message, field)) != 0; case FieldDescriptor::CPPTYPE_ENUM: + if (schema_.IsEnum8(field)) { + return GetRaw<uint8_t>(message, field) != 0; + } else if (schema_.IsEnum16(field)) { + return GetRaw<uint16_t>(message, field) != 0; + } return GetRaw<int>(message, field) != 0; case FieldDescriptor::CPPTYPE_STRING: switch (field->cpp_string_type()) { @@ -3795,6 +3837,13 @@ } return std::monostate{}; }; + const auto enum_rep = [&]() -> FieldOptions::EnumRep { + if (field->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { + if (schema_.IsEnum8(field)) return FieldOptions::kEnum8; + if (schema_.IsEnum16(field)) return FieldOptions::kEnum16; + } + return FieldOptions::kEnum32; + }; fields.push_back({ field, // static_cast<int>(schema_.HasBitIndex(field)), @@ -3807,6 +3856,7 @@ /* use_direct_tcparser_table */ false, schema_.IsSplit(field), str_options(), + enum_rep(), }); } std::sort(fields.begin(), fields.end(), [](const auto& a, const auto& b) {
diff --git a/src/google/protobuf/generated_message_reflection.h b/src/google/protobuf/generated_message_reflection.h index 66494d9..029abfd 100644 --- a/src/google/protobuf/generated_message_reflection.h +++ b/src/google/protobuf/generated_message_reflection.h
@@ -65,10 +65,14 @@ inline constexpr uint32_t kLazyOffsetTag = 0x40000000u; inline constexpr uint32_t kInlinedOffsetTag = 0x40000000u; inline constexpr uint32_t kMicroStringOffsetTag = 0x20000000u; +inline constexpr uint32_t kEnum8OffsetTag = 0x20000000u; +inline constexpr uint32_t kEnum16OffsetTag = 0x40000000u; +inline constexpr uint32_t kEnumSignedOffsetTag = 0x10000000u; -inline constexpr uint32_t kAllOffsetTags = kSplitFieldOffsetTag | - kLazyOffsetTag | kInlinedOffsetTag | - kMicroStringOffsetTag; +inline constexpr uint32_t kAllOffsetTags = + kSplitFieldOffsetTag | kLazyOffsetTag | kInlinedOffsetTag | + kMicroStringOffsetTag | kEnum8OffsetTag | kEnum16OffsetTag | + kEnumSignedOffsetTag; // Structs that the code generator emits directly to describe a message. // These should never used directly except to build a ReflectionSchema @@ -124,12 +128,21 @@ // weak_field_map_offset: If the message proto has weak fields, this is the // offset of _weak_field_map_ in the generated proto. Otherwise // -1. -class ReflectionSchema { +class PROTOBUF_EXPORT ReflectionSchema { public: ReflectionSchema(const Message* default_instance, const uint32_t* offsets, const uint32_t* has_bit_indices, int has_bits_offset, int extensions_offset, int oneof_case_offset, - int object_size, int split_offset, int sizeof_split); + int object_size, int split_offset, int sizeof_split) + : default_instance_(default_instance), + offsets_(offsets), + has_bit_indices_(has_bit_indices), + has_bits_offset_(has_bits_offset), + extensions_offset_(extensions_offset), + oneof_case_offset_(oneof_case_offset), + object_size_(object_size), + split_offset_(split_offset), + sizeof_split_(sizeof_split) {} // Helper function to transform migration schema into reflection schema. static ReflectionSchema MigrationToReflectionSchema( @@ -158,6 +171,18 @@ return IsMicroString(offsets_[field->index()], field->type()); } + bool IsEnum8(const FieldDescriptor* field) const { + return IsEnum8(offsets_[field->index()], field->type()); + } + + bool IsEnum16(const FieldDescriptor* field) const { + return IsEnum16(offsets_[field->index()], field->type()); + } + + bool IsEnumSigned(const FieldDescriptor* field) const { + return IsEnumSigned(offsets_[field->index()], field->type()); + } + uint32_t GetOneofCaseOffset(const OneofDescriptor* oneof_descriptor) const { return static_cast<uint32_t>(oneof_case_offset_) + static_cast<uint32_t>( @@ -263,6 +288,19 @@ return (v & kMicroStringOffsetTag) != 0u; } + static bool IsEnum8(uint32_t v, FieldDescriptor::Type type) { + return type == FieldDescriptor::TYPE_ENUM && (v & kEnum8OffsetTag) != 0u; + } + + static bool IsEnum16(uint32_t v, FieldDescriptor::Type type) { + return type == FieldDescriptor::TYPE_ENUM && (v & kEnum16OffsetTag) != 0u; + } + + static bool IsEnumSigned(uint32_t v, FieldDescriptor::Type type) { + return type == FieldDescriptor::TYPE_ENUM && + (v & kEnumSignedOffsetTag) != 0u; + } + const Message* default_instance_; const uint32_t* offsets_; const uint32_t* has_bit_indices_;
diff --git a/src/google/protobuf/generated_message_reflection_unittest.cc b/src/google/protobuf/generated_message_reflection_unittest.cc index e76f927..2fbf3a0 100644 --- a/src/google/protobuf/generated_message_reflection_unittest.cc +++ b/src/google/protobuf/generated_message_reflection_unittest.cc
@@ -22,6 +22,8 @@ #include "google/protobuf/generated_message_reflection.h" #include <cstddef> +#include <cstdint> +#include <cstring> #include <memory> #include <string> #include <vector> @@ -2043,6 +2045,69 @@ "cpp.file.options.test"); } +TEST(GeneratedMessageReflection, Enum8And16BitFields) { + const Descriptor* desc = unittest::TestAllTypes::descriptor(); + const FieldDescriptor* field = desc->FindFieldByName("optional_nested_enum"); + ASSERT_NE(field, nullptr); + + std::vector<uint32_t> offsets(desc->field_count(), 0); + std::vector<uint32_t> has_bits(desc->field_count(), 0); + + // Test 8-bit unsigned enum + { + offsets[field->index()] = 100 | internal::kEnum8OffsetTag; + internal::ReflectionSchema schema( + &unittest::TestAllTypes::default_instance(), offsets.data(), + has_bits.data(), /*has_bits_offset=*/-1, /*extensions_offset=*/-1, + /*oneof_case_offset=*/-1, sizeof(unittest::TestAllTypes), + /*split_offset=*/-1, /*sizeof_split=*/-1); + EXPECT_TRUE(schema.IsEnum8(field)); + EXPECT_FALSE(schema.IsEnum16(field)); + EXPECT_FALSE(schema.IsEnumSigned(field)); + } + + // Test 8-bit signed enum + { + offsets[field->index()] = + 100 | internal::kEnum8OffsetTag | internal::kEnumSignedOffsetTag; + internal::ReflectionSchema schema( + &unittest::TestAllTypes::default_instance(), offsets.data(), + has_bits.data(), /*has_bits_offset=*/-1, /*extensions_offset=*/-1, + /*oneof_case_offset=*/-1, sizeof(unittest::TestAllTypes), + /*split_offset=*/-1, /*sizeof_split=*/-1); + EXPECT_TRUE(schema.IsEnum8(field)); + EXPECT_FALSE(schema.IsEnum16(field)); + EXPECT_TRUE(schema.IsEnumSigned(field)); + } + + // Test 16-bit unsigned enum + { + offsets[field->index()] = 100 | internal::kEnum16OffsetTag; + internal::ReflectionSchema schema( + &unittest::TestAllTypes::default_instance(), offsets.data(), + has_bits.data(), /*has_bits_offset=*/-1, /*extensions_offset=*/-1, + /*oneof_case_offset=*/-1, sizeof(unittest::TestAllTypes), + /*split_offset=*/-1, /*sizeof_split=*/-1); + EXPECT_FALSE(schema.IsEnum8(field)); + EXPECT_TRUE(schema.IsEnum16(field)); + EXPECT_FALSE(schema.IsEnumSigned(field)); + } + + // Test 16-bit signed enum + { + offsets[field->index()] = + 100 | internal::kEnum16OffsetTag | internal::kEnumSignedOffsetTag; + internal::ReflectionSchema schema( + &unittest::TestAllTypes::default_instance(), offsets.data(), + has_bits.data(), /*has_bits_offset=*/-1, /*extensions_offset=*/-1, + /*oneof_case_offset=*/-1, sizeof(unittest::TestAllTypes), + /*split_offset=*/-1, /*sizeof_split=*/-1); + EXPECT_FALSE(schema.IsEnum8(field)); + EXPECT_TRUE(schema.IsEnum16(field)); + EXPECT_TRUE(schema.IsEnumSigned(field)); + } +} + } // namespace } // namespace protobuf } // namespace google
diff --git a/src/google/protobuf/generated_message_tctable_gen.cc b/src/google/protobuf/generated_message_tctable_gen.cc index f8d82ac..b9daf60 100644 --- a/src/google/protobuf/generated_message_tctable_gen.cc +++ b/src/google/protobuf/generated_message_tctable_gen.cc
@@ -190,22 +190,46 @@ picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastF64); break; case FieldDescriptor::TYPE_ENUM: - if (TreatEnumAsInt(field)) { - picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastV32); + if (!field->is_repeated() && options.is_enum_8()) { + if (TreatEnumAsInt(field)) { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastV8); + } else { + int32_t first, last; + if (GetEnumValidationRange(field->enum_type(), first, last)) { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastEr8); + } else { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastEv8); + } + } + } else if (!field->is_repeated() && options.is_enum_16()) { + if (TreatEnumAsInt(field)) { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastV16); + } else { + int32_t first, last; + if (GetEnumValidationRange(field->enum_type(), first, last)) { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastEr16); + } else { + picked = PROTOBUF_PICK_SINGLE_FUNCTION(kFastEv16); + } + } } else { - switch (GetEnumRangeInfo(field, info.aux_idx)) { - case EnumRangeInfo::kNone: - picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEv); - break; - case EnumRangeInfo::kContiguous: - picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr); - break; - case EnumRangeInfo::kContiguous0: - picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr0); - break; - case EnumRangeInfo::kContiguous1: - picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr1); - break; + if (TreatEnumAsInt(field)) { + picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastV32); + } else { + switch (GetEnumRangeInfo(field, info.aux_idx)) { + case EnumRangeInfo::kNone: + picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEv); + break; + case EnumRangeInfo::kContiguous: + picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr); + break; + case EnumRangeInfo::kContiguous0: + picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr0); + break; + case EnumRangeInfo::kContiguous1: + picked = PROTOBUF_PICK_PACKABLE_FUNCTION(kFastEr1); + break; + } } } break; @@ -255,6 +279,10 @@ return false; } + if (field->is_repeated() && (options.is_enum_8() || options.is_enum_16())) { + return false; + } + if (HasLazyRep(field, options) && !message_options.uses_codegen) { // Can't use TDP on lazy fields if we can't do codegen. return false; @@ -548,24 +576,50 @@ : fl::kBool; break; case FieldDescriptor::TYPE_ENUM: - if (TreatEnumAsInt(field)) { - // No validation is required. - type_card |= field->is_repeated() && field->is_packed() - ? fl::kPackedOpenEnum - : fl::kOpenEnum; - } else { - int32_t first; - int32_t last; - if (GetEnumValidationRange(field->enum_type(), first, last)) { - // Validation is done by range check (start/length in FieldAux). - type_card |= field->is_repeated() && field->is_packed() - ? fl::kPackedEnumRange - : fl::kEnumRange; + if (!field->is_repeated() && options.is_enum_8()) { + if (TreatEnumAsInt(field)) { + type_card |= fl::kOpenEnum8; } else { - // Validation uses the generated _IsValid function. + int32_t first; + int32_t last; + if (GetEnumValidationRange(field->enum_type(), first, last)) { + type_card |= fl::kEnumRange8; + } else { + type_card |= fl::kEnum8; + } + } + } else if (!field->is_repeated() && options.is_enum_16()) { + if (TreatEnumAsInt(field)) { + type_card |= fl::kOpenEnum16; + } else { + int32_t first; + int32_t last; + if (GetEnumValidationRange(field->enum_type(), first, last)) { + type_card |= fl::kEnumRange16; + } else { + type_card |= fl::kEnum16; + } + } + } else { + if (TreatEnumAsInt(field)) { + // No validation is required. type_card |= field->is_repeated() && field->is_packed() - ? fl::kPackedEnum - : fl::kEnum; + ? fl::kPackedOpenEnum + : fl::kOpenEnum; + } else { + int32_t first; + int32_t last; + if (GetEnumValidationRange(field->enum_type(), first, last)) { + // Validation is done by range check (start/length in FieldAux). + type_card |= field->is_repeated() && field->is_packed() + ? fl::kPackedEnumRange + : fl::kEnumRange; + } else { + // Validation uses the generated _IsValid function. + type_card |= field->is_repeated() && field->is_packed() + ? fl::kPackedEnum + : fl::kEnum; + } } } break;
diff --git a/src/google/protobuf/generated_message_tctable_gen.h b/src/google/protobuf/generated_message_tctable_gen.h index fd1e0e6..e7e55bd 100644 --- a/src/google/protobuf/generated_message_tctable_gen.h +++ b/src/google/protobuf/generated_message_tctable_gen.h
@@ -86,6 +86,11 @@ using StrOptions = std::variant<std::monostate, StringInlined, MicroString>; StrOptions str_options; + + enum EnumRep { kEnum32 = 0, kEnum8, kEnum16 }; + EnumRep enum_rep = kEnum32; + bool is_enum_8() const { return enum_rep == kEnum8; } + bool is_enum_16() const { return enum_rep == kEnum16; } }; struct FieldEntryInfo;
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h index 34a2a82..c860e57 100644 --- a/src/google/protobuf/generated_message_tctable_impl.h +++ b/src/google/protobuf/generated_message_tctable_impl.h
@@ -127,6 +127,7 @@ // Numeric types (used for optional and repeated fields): kRep8Bits = 0, + kRep16Bits = 1 << kRepShift, kRep32Bits = 2 << kRepShift, kRep64Bits = 3 << kRepShift, // String types: @@ -201,6 +202,14 @@ // Numeric types: kBool = 0 | kFkVarint | kRep8Bits, + kEnum8 = 0 | kFkVarint | kRep8Bits | kFmtEnum | kTvEnum, + kEnumRange8 = 0 | kFkVarint | kRep8Bits | kFmtEnum | kTvRange, + kOpenEnum8 = 0 | kFkVarint | kRep8Bits | kFmtEnum, + + kEnum16 = 0 | kFkVarint | kRep16Bits | kFmtEnum | kTvEnum, + kEnumRange16 = 0 | kFkVarint | kRep16Bits | kFmtEnum | kTvRange, + kOpenEnum16 = 0 | kFkVarint | kRep16Bits | kFmtEnum, + kFixed32 = 0 | kFkFixed | kRep32Bits | kFmtUnsigned, kUInt32 = 0 | kFkVarint | kRep32Bits | kFmtUnsigned, kSFixed32 = 0 | kFkFixed | kRep32Bits | kFmtSigned, @@ -251,6 +260,8 @@ } // namespace field_layout #ifndef NDEBUG +[[noreturn]] PROTOBUF_EXPORT void AlignFail(std::integral_constant<size_t, 2>, + std::uintptr_t address); [[noreturn]] PROTOBUF_EXPORT void AlignFail(std::integral_constant<size_t, 4>, std::uintptr_t address); [[noreturn]] PROTOBUF_EXPORT void AlignFail(std::integral_constant<size_t, 8>, @@ -334,6 +345,7 @@ #define PROTOBUF_TC_PARSE_FUNCTION_LIST \ /* These functions have the Fast entry ABI */ \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastV8) \ + PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastV16) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastV32) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastV64) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastZ32) \ @@ -344,6 +356,10 @@ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastEr) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastEr0) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_PACKED(FastEr1) \ + PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastEv8) \ + PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastEr8) \ + PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastEv16) \ + PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastEr16) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_REPEATED(FastB) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_REPEATED(FastU) \ PROTOBUF_TC_PARSE_FUNCTION_LIST_SINGLE(FastBi) \ @@ -481,6 +497,10 @@ PROTOBUF_TC_PARAM_DECL); PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastV8P2( PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastV16S1( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastV16S2( + PROTOBUF_TC_PARAM_DECL); PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastV32S1( PROTOBUF_TC_PARAM_DECL); PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastV32S2( @@ -537,14 +557,17 @@ if (sizeof(FieldType) == 1) { return &FastV8S1; } + if (sizeof(FieldType) == 2) { + return &FastV16S1; + } if (sizeof(FieldType) == 4) { return &FastV32S1; } if (sizeof(FieldType) == 8) { return &FastV64S1; } - static_assert(sizeof(FieldType) == 1 || sizeof(FieldType) == 4 || - sizeof(FieldType) == 8, + static_assert(sizeof(FieldType) == 1 || sizeof(FieldType) == 2 || + sizeof(FieldType) == 4 || sizeof(FieldType) == 8, ""); ABSL_LOG(FATAL) << "This should be unreachable"; } @@ -604,6 +627,24 @@ PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEr1P2( PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEr8S1( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEr8S2( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEv8S1( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEv8S2( + PROTOBUF_TC_PARAM_DECL); + + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEr16S1( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEr16S2( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEv16S1( + PROTOBUF_TC_PARAM_DECL); + PROTOBUF_NOINLINE PROTOBUF_CC static const char* FastEv16S2( + PROTOBUF_TC_PARAM_DECL); + // Functions referenced by generated fast tables (string types): // B: bytes U: UTF-8 string // (empty): ArenaStringPtr i: InlinedString c: Cord m: MicroString @@ -972,7 +1013,7 @@ PROTOBUF_TC_PARAM_DECL); // Implementations for fast enum field parsing functions: - template <typename TagType, uint16_t xform_val> + template <typename FieldType, typename TagType, uint16_t xform_val> PROTOBUF_CC static inline const char* SingularEnum(PROTOBUF_TC_PARAM_DECL); template <typename TagType, uint8_t min> PROTOBUF_CC static inline const char* SingularEnumSmallRange(
diff --git a/src/google/protobuf/generated_message_tctable_lite.cc b/src/google/protobuf/generated_message_tctable_lite.cc index c4c9023..736fd41 100644 --- a/src/google/protobuf/generated_message_tctable_lite.cc +++ b/src/google/protobuf/generated_message_tctable_lite.cc
@@ -64,6 +64,10 @@ ////////////////////////////////////////////////////////////////////////////// #ifndef NDEBUG +[[noreturn]] void AlignFail(std::integral_constant<size_t, 2>, + std::uintptr_t address) { + ABSL_LOG(FATAL) << "Unaligned (2) access at " << address; +} [[noreturn]] void AlignFail(std::integral_constant<size_t, 4>, std::uintptr_t address) { ABSL_LOG(FATAL) << "Unaligned (4) access at " << address; @@ -140,8 +144,14 @@ if (has_bit) break; switch (entry.type_card & fl::kRepMask) { case fl::kRep8Bits: - if (RefAt<bool>(base, entry.offset) != - RefAt<bool>(default_base, entry.offset)) { + if (RefAt<uint8_t>(base, entry.offset) != + RefAt<uint8_t>(default_base, entry.offset)) { + return make_error_status(); + } + break; + case fl::kRep16Bits: + if (RefAt<uint16_t>(base, entry.offset) != + RefAt<uint16_t>(default_base, entry.offset)) { return make_error_status(); } break; @@ -1243,6 +1253,14 @@ PROTOBUF_NOINLINE const char* TcParser::FastV8P2(PROTOBUF_TC_PARAM_DECL) { PROTOBUF_MUSTTAIL return PackedVarint<bool, uint16_t>(PROTOBUF_TC_PARAM_PASS); } +PROTOBUF_NOINLINE const char* TcParser::FastV16S1(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularVarint<uint16_t, uint8_t>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastV16S2(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularVarint<uint16_t, uint16_t>( + PROTOBUF_TC_PARAM_PASS); +} PROTOBUF_NOINLINE const char* TcParser::FastV32P1(PROTOBUF_TC_PARAM_DECL) { PROTOBUF_MUSTTAIL return PackedVarint<uint32_t, uint8_t>( PROTOBUF_TC_PARAM_PASS); @@ -1313,7 +1331,7 @@ PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_NO_DATA_PASS); } -template <typename TagType, uint16_t xform_val> +template <typename FieldType, typename TagType, uint16_t xform_val> PROTOBUF_ALWAYS_INLINE const char* TcParser::SingularEnum( PROTOBUF_TC_PARAM_DECL) { if (ABSL_PREDICT_FALSE(data.coded_tag<TagType>() != 0)) { @@ -1334,24 +1352,70 @@ PROTOBUF_MUSTTAIL return FastUnknownEnumFallback(PROTOBUF_TC_PARAM_PASS); } SetCachedHasBit(hasbits, data.hasbit_idx()); - RefAt<int32_t>(msg, data.offset()) = tmp; + RefAt<FieldType>(msg, data.offset()) = static_cast<FieldType>(tmp); PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_NO_DATA_PASS); } PROTOBUF_NOINLINE const char* TcParser::FastErS1(PROTOBUF_TC_PARAM_DECL) { - PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, field_layout::kTvRange>( + PROTOBUF_MUSTTAIL return SingularEnum<int32_t, uint8_t, + field_layout::kTvRange>( PROTOBUF_TC_PARAM_PASS); } PROTOBUF_NOINLINE const char* TcParser::FastErS2(PROTOBUF_TC_PARAM_DECL) { - PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, field_layout::kTvRange>( + PROTOBUF_MUSTTAIL return SingularEnum<int32_t, uint16_t, + field_layout::kTvRange>( PROTOBUF_TC_PARAM_PASS); } PROTOBUF_NOINLINE const char* TcParser::FastEvS1(PROTOBUF_TC_PARAM_DECL) { - PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, field_layout::kTvEnum>( + PROTOBUF_MUSTTAIL return SingularEnum<int32_t, uint8_t, + field_layout::kTvEnum>( PROTOBUF_TC_PARAM_PASS); } PROTOBUF_NOINLINE const char* TcParser::FastEvS2(PROTOBUF_TC_PARAM_DECL) { - PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, field_layout::kTvEnum>( + PROTOBUF_MUSTTAIL return SingularEnum<int32_t, uint16_t, + field_layout::kTvEnum>( + PROTOBUF_TC_PARAM_PASS); +} + +PROTOBUF_NOINLINE const char* TcParser::FastEr8S1(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, uint8_t, + field_layout::kTvRange>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEr8S2(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, uint16_t, + field_layout::kTvRange>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEv8S1(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, uint8_t, + field_layout::kTvEnum>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEv8S2(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint8_t, uint16_t, + field_layout::kTvEnum>( + PROTOBUF_TC_PARAM_PASS); +} + +PROTOBUF_NOINLINE const char* TcParser::FastEr16S1(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, uint8_t, + field_layout::kTvRange>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEr16S2(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, uint16_t, + field_layout::kTvRange>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEv16S1(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, uint8_t, + field_layout::kTvEnum>( + PROTOBUF_TC_PARAM_PASS); +} +PROTOBUF_NOINLINE const char* TcParser::FastEv16S2(PROTOBUF_TC_PARAM_DECL) { + PROTOBUF_MUSTTAIL return SingularEnum<uint16_t, uint16_t, + field_layout::kTvEnum>( PROTOBUF_TC_PARAM_PASS); } @@ -2263,6 +2327,21 @@ } else if (is_zigzag) { tmp = WireFormatLite::ZigZagDecode32(static_cast<uint32_t>(tmp)); } + } else if (rep == field_layout::kRep16Bits) { + if (is_validated_enum) { + if (!EnumIsValidAux(tmp, xform_val, *table->field_aux(&entry))) { + ptr = ptr2; + PROTOBUF_MUSTTAIL return MpUnknownEnumFallback(PROTOBUF_TC_PARAM_PASS); + } + } + } else { + ABSL_DCHECK_EQ(rep, static_cast<uint16_t>(field_layout::kRep8Bits)); + if (is_validated_enum) { + if (!EnumIsValidAux(tmp, xform_val, *table->field_aux(&entry))) { + ptr = ptr2; + PROTOBUF_MUSTTAIL return MpUnknownEnumFallback(PROTOBUF_TC_PARAM_PASS); + } + } } // Mark the field as present: @@ -2279,9 +2358,15 @@ RefAt<uint64_t>(base, entry.offset) = tmp; } else if (rep == field_layout::kRep32Bits) { RefAt<uint32_t>(base, entry.offset) = static_cast<uint32_t>(tmp); + } else if (rep == field_layout::kRep16Bits) { + RefAt<uint16_t>(base, entry.offset) = static_cast<uint16_t>(tmp); } else { ABSL_DCHECK_EQ(rep, static_cast<uint16_t>(field_layout::kRep8Bits)); - RefAt<bool>(base, entry.offset) = static_cast<bool>(tmp); + if ((type_card & field_layout::kFmtMask) == field_layout::kFmtEnum) { + RefAt<uint8_t>(base, entry.offset) = static_cast<uint8_t>(tmp); + } else { + RefAt<bool>(base, entry.offset) = static_cast<bool>(tmp); + } } PROTOBUF_MUSTTAIL return ToTagDispatch(PROTOBUF_TC_PARAM_NO_DATA_PASS); @@ -3209,6 +3294,12 @@ case fl::kFkPackedFixed: { switch (type_card & ~fl::kFcMask & ~fl::kSplitMask) { PROTOBUF_INTERNAL_TYPE_CARD_CASE(Bool); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(Enum8); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(EnumRange8); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(OpenEnum8); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(Enum16); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(EnumRange16); + PROTOBUF_INTERNAL_TYPE_CARD_CASE(OpenEnum16); PROTOBUF_INTERNAL_TYPE_CARD_CASE(Fixed32); PROTOBUF_INTERNAL_TYPE_CARD_CASE(UInt32); PROTOBUF_INTERNAL_TYPE_CARD_CASE(SFixed32);
diff --git a/src/google/protobuf/generated_message_tctable_lite_test.cc b/src/google/protobuf/generated_message_tctable_lite_test.cc index ad4afdc..ae30a08 100644 --- a/src/google/protobuf/generated_message_tctable_lite_test.cc +++ b/src/google/protobuf/generated_message_tctable_lite_test.cc
@@ -134,7 +134,7 @@ }; uint8_t serialize_buffer[64]; - for (int size : {8, 32, 64}) { + for (int size : {8, 16, 32, 64}) { SCOPED_TRACE(size); auto next_i = [](uint64_t i) { // if i + 1 is a power of two, return that. @@ -201,6 +201,9 @@ case 8: fn = &TcParser::FastV8S1; break; + case 16: + fn = &TcParser::FastV16S1; + break; case 32: fn = &TcParser::FastV32S1; break; @@ -239,6 +242,13 @@ EXPECT_EQ(actual_field, static_cast<decltype(actual_field)>(i)) // << " hex: " << absl::StrCat(absl::Hex(actual_field)); }; break; + case 16: { + ASSERT_EQ(end_ptr - ptr, serialized.size()); + + auto actual_field = ReadAndReset<uint16_t>(&fake_msg[kFieldOffset]); + EXPECT_EQ(actual_field, static_cast<decltype(actual_field)>(i)) // + << " hex: " << absl::StrCat(absl::Hex(actual_field)); + }; break; case 32: { ASSERT_TRUE(end_ptr); ASSERT_EQ(end_ptr - ptr, serialized.size()); @@ -1071,6 +1081,160 @@ (void)msg->ParseFromString(payload); } +TEST(TcParserTest, Enum8And16TypeCardToString) { + namespace fl = internal::field_layout; + EXPECT_EQ( + TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | fl::kEnum8), + "::_fl::kFcOptional | ::_fl::kEnum8"); + EXPECT_EQ(TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | + fl::kEnumRange8), + "::_fl::kFcOptional | ::_fl::kEnumRange8"); + EXPECT_EQ( + TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | fl::kOpenEnum8), + "::_fl::kFcOptional | ::_fl::kOpenEnum8"); + EXPECT_EQ( + TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | fl::kEnum16), + "::_fl::kFcOptional | ::_fl::kEnum16"); + EXPECT_EQ(TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | + fl::kEnumRange16), + "::_fl::kFcOptional | ::_fl::kEnumRange16"); + EXPECT_EQ(TypeCardToString(static_cast<uint16_t>(fl::kFcOptional) | + fl::kOpenEnum16), + "::_fl::kFcOptional | ::_fl::kOpenEnum16"); +} + +TEST(TcParserTest, FastEnum8And16Parsing) { + constexpr uint8_t kHasBitsOffset = 4; + constexpr uint8_t kHasBitIndex = 0; + constexpr uint8_t kFieldOffset = 24; + + const ClassData class_data(nullptr, nullptr, MessageCreator(), nullptr, + nullptr, nullptr, nullptr, + /*cached_size_offset=*/16, "type_name"); + + alignas(16) char fake_msg[64]; + memset(fake_msg, kDND, sizeof(fake_msg)); + memset(&fake_msg[kHasBitsOffset], 0, sizeof(uint32_t)); + + TcParseTable<1, 1, 2, 0, 2> parse_table = { + // header: + { + kHasBitsOffset, + 0, + 1, + 0, + offsetof(decltype(parse_table), field_lookup_table), + 0xFFFFFFFF - 1, + offsetof(decltype(parse_table), field_entries), + 1, + 2, + offsetof(decltype(parse_table), aux_entries), + &class_data, + nullptr, + &FastParserGaveUp, + }, + // Fast entries: + {{ + {}, + }}, + // Field Lookup Table: + {{65535, 65535}}, + // Field Entries: + {{ + {kFieldOffset, kHasBitsOffset + 0, 0, field_layout::kEnum8}, + }}, + // Aux Entries: + {{ + {0, 10}, // range 0..10 + {FieldAuxEnumData{}, + EnumTraits<proto2_unittest::ForeignEnum>::validation_data()}, + }}, + }; + + uint8_t serialize_buffer[64]; + auto serialize_ptr = WireFormatLite::WriteUInt32ToArray( + /* field_number= */ 1, 5, serialize_buffer); + absl::string_view serialized{ + reinterpret_cast<char*>(&serialize_buffer[0]), + static_cast<size_t>(serialize_ptr - serialize_buffer)}; + + // Test FastEr8S1 (range validation for 8-bit enum) + { + memset(fake_msg, kDND, sizeof(fake_msg)); + memset(&fake_msg[kHasBitsOffset], 0, sizeof(uint32_t)); + const char* ptr = nullptr; + ParseContext ctx(io::CodedInputStream::GetDefaultRecursionLimit(), + /* aliasing= */ false, &ptr, serialized); + TcFieldData data(/*coded_tag=*/8, kHasBitIndex, /*aux_idx=*/0, + kFieldOffset); + const char* end_ptr = TcParser::FastEr8S1( + reinterpret_cast<MessageLite*>(fake_msg), ptr, &ctx, + Xor2SerializedBytes(data, ptr), &parse_table.header, /*hasbits=*/0); + ASSERT_EQ(end_ptr - ptr, serialized.size()); + auto actual_field = ReadAndReset<uint8_t>(&fake_msg[kFieldOffset]); + EXPECT_EQ(actual_field, 5); + auto hasbits = ReadAndReset<uint32_t>(&fake_msg[kHasBitsOffset]); + EXPECT_EQ(hasbits, 1 << kHasBitIndex); + } + + // Test FastEv8S1 (function validation for 8-bit enum) + { + memset(fake_msg, kDND, sizeof(fake_msg)); + memset(&fake_msg[kHasBitsOffset], 0, sizeof(uint32_t)); + const char* ptr = nullptr; + ParseContext ctx(io::CodedInputStream::GetDefaultRecursionLimit(), + /* aliasing= */ false, &ptr, serialized); + TcFieldData data(/*coded_tag=*/8, kHasBitIndex, /*aux_idx=*/1, + kFieldOffset); + const char* end_ptr = TcParser::FastEv8S1( + reinterpret_cast<MessageLite*>(fake_msg), ptr, &ctx, + Xor2SerializedBytes(data, ptr), &parse_table.header, /*hasbits=*/0); + ASSERT_EQ(end_ptr - ptr, serialized.size()); + auto actual_field = ReadAndReset<uint8_t>(&fake_msg[kFieldOffset]); + EXPECT_EQ(actual_field, 5); + auto hasbits = ReadAndReset<uint32_t>(&fake_msg[kHasBitsOffset]); + EXPECT_EQ(hasbits, 1 << kHasBitIndex); + } + + // Test FastEr16S1 (range validation for 16-bit enum) + { + memset(fake_msg, kDND, sizeof(fake_msg)); + memset(&fake_msg[kHasBitsOffset], 0, sizeof(uint32_t)); + const char* ptr = nullptr; + ParseContext ctx(io::CodedInputStream::GetDefaultRecursionLimit(), + /* aliasing= */ false, &ptr, serialized); + TcFieldData data(/*coded_tag=*/8, kHasBitIndex, /*aux_idx=*/0, + kFieldOffset); + const char* end_ptr = TcParser::FastEr16S1( + reinterpret_cast<MessageLite*>(fake_msg), ptr, &ctx, + Xor2SerializedBytes(data, ptr), &parse_table.header, /*hasbits=*/0); + ASSERT_EQ(end_ptr - ptr, serialized.size()); + auto actual_field = ReadAndReset<uint16_t>(&fake_msg[kFieldOffset]); + EXPECT_EQ(actual_field, 5); + auto hasbits = ReadAndReset<uint32_t>(&fake_msg[kHasBitsOffset]); + EXPECT_EQ(hasbits, 1 << kHasBitIndex); + } + + // Test FastEv16S1 (function validation for 16-bit enum) + { + memset(fake_msg, kDND, sizeof(fake_msg)); + memset(&fake_msg[kHasBitsOffset], 0, sizeof(uint32_t)); + const char* ptr = nullptr; + ParseContext ctx(io::CodedInputStream::GetDefaultRecursionLimit(), + /* aliasing= */ false, &ptr, serialized); + TcFieldData data(/*coded_tag=*/8, kHasBitIndex, /*aux_idx=*/1, + kFieldOffset); + const char* end_ptr = TcParser::FastEv16S1( + reinterpret_cast<MessageLite*>(fake_msg), ptr, &ctx, + Xor2SerializedBytes(data, ptr), &parse_table.header, /*hasbits=*/0); + ASSERT_EQ(end_ptr - ptr, serialized.size()); + auto actual_field = ReadAndReset<uint16_t>(&fake_msg[kFieldOffset]); + EXPECT_EQ(actual_field, 5); + auto hasbits = ReadAndReset<uint32_t>(&fake_msg[kHasBitsOffset]); + EXPECT_EQ(hasbits, 1 << kHasBitIndex); + } +} + } // namespace internal } // namespace protobuf } // namespace google
diff --git a/src/google/protobuf/message.h b/src/google/protobuf/message.h index 63b8396..c7731d7 100644 --- a/src/google/protobuf/message.h +++ b/src/google/protobuf/message.h
@@ -1901,6 +1901,13 @@ << error(); } else { auto cpp_type = field->cpp_type(); + // Shrunken closed enums are stored as 1-byte (uint8_t/int8_t) or 2-byte + // (uint16_t/int16_t) integers in generated message structs. + if (cpp_type == field->CPPTYPE_ENUM && + (std::is_same_v<T, uint8_t> || std::is_same_v<T, int8_t> || + std::is_same_v<T, uint16_t> || std::is_same_v<T, int16_t>)) { + return; + } // Collapse ENUM to INT32 because they are the same through reflection. if (cpp_type == field->CPPTYPE_ENUM) cpp_type = field->CPPTYPE_INT32; ABSL_DCHECK_EQ(+cpp_type, +internal::GetCppType<T>()) << error();