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();