This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new ddcf555fb fix(cpp): align unsigned struct default encoding (#3754)
ddcf555fb is described below

commit ddcf555fb553ed80d5002bc9f2f2647da279d529
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Jun 12 18:04:44 2026 +0800

    fix(cpp): align unsigned struct default encoding (#3754)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    Closes #3746
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 AGENTS.md                                  |   1 +
 cpp/fory/serialization/struct_serializer.h | 273 +++++++++++++++++++----------
 cpp/fory/serialization/struct_test.cc      | 174 ++++++++++++++++++
 cpp/fory/serialization/type_resolver.h     |  22 +--
 4 files changed, 371 insertions(+), 99 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 8b4d2644f..02e44f69a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -144,6 +144,7 @@ This is the entry point for AI guidance in Apache Fory. 
Read this file first, th
 - If xlang behavior or type mapping changes, run 
`org.apache.fory.xlang.CPPXlangTest`, `org.apache.fory.xlang.CSharpXlangTest`, 
`org.apache.fory.xlang.RustXlangTest`, `org.apache.fory.xlang.GoXlangTest`, and 
`org.apache.fory.xlang.PythonXlangTest`.
 - If Swift xlang behavior changes, run `org.apache.fory.xlang.SwiftXlangTest` 
too.
 - For performance regressions or optimizations, profile or otherwise measure 
the current branch and a fresh `apache/main` baseline before changing code; 
optimize the measured hotspot, not guessed code.
+- When comparing benchmark results against `apache/main`, use a separate 
sibling worktree named `fory-benchmark-baseline` by default. Before creating a 
new worktree, check whether `../fory-benchmark-baseline` already exists and 
reuse it to avoid repeated benchmark dependency rebuilds. Always fetch and sync 
that baseline worktree to the latest `apache/main` before measuring it, and 
store benchmark result files under that worktree so older runs remain available 
as reference data. Treat sto [...]
 - Do not change protocol behavior, benchmark payloads, or public APIs solely 
to manufacture performance wins.
 - For performance work, run the relevant benchmark immediately after each 
change and report the command plus before/after numbers.
 - For performance-optimization rounds, append the hypothesis, change, 
benchmark command, before/after numbers, and keep/revert decision to 
`tasks/perf_optimization_rounds.md`.
diff --git a/cpp/fory/serialization/struct_serializer.h 
b/cpp/fory/serialization/struct_serializer.h
index 2954c850e..4da4f7751 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -145,8 +145,7 @@ FORY_ALWAYS_INLINE uint32_t put_primitive_at(T value, 
Buffer &buffer,
     return buffer.put_var_uint32(offset, zigzag);
   } else if constexpr (std::is_same_v<T, uint32_t> ||
                        std::is_same_v<T, unsigned int>) {
-    buffer.unsafe_put<uint32_t>(offset, static_cast<uint32_t>(value));
-    return 4;
+    return buffer.put_var_uint32(offset, static_cast<uint32_t>(value));
   } else if constexpr (std::is_same_v<T, int64_t> ||
                        std::is_same_v<T, long long>) {
     // varint64 with zigzag encoding
@@ -156,8 +155,7 @@ FORY_ALWAYS_INLINE uint32_t put_primitive_at(T value, 
Buffer &buffer,
     return buffer.put_var_uint64(offset, zigzag);
   } else if constexpr (std::is_same_v<T, uint64_t> ||
                        std::is_same_v<T, unsigned long long>) {
-    buffer.unsafe_put<uint64_t>(offset, static_cast<uint64_t>(value));
-    return 8;
+    return buffer.put_var_uint64(offset, static_cast<uint64_t>(value));
   } else if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, int>) {
     buffer.unsafe_put<int32_t>(offset, static_cast<int32_t>(value));
     return 4;
@@ -315,8 +313,10 @@ constexpr bool configurable_int_is_fixed() {
   if constexpr (is_signed_configurable_int_v<FieldType>) {
     return field_int_encoding<FieldType, StructT, Index>() == Encoding::Fixed;
   } else if constexpr (is_unsigned_configurable_int_v<FieldType>) {
-    constexpr auto enc = field_int_encoding<FieldType, StructT, Index>();
-    return enc != Encoding::Varint && enc != Encoding::Tagged;
+    // uint32_t/uint64_t default to their Serializer type IDs (VAR_UINT*).
+    // Treat only explicit Fixed as fixed so field metadata and field bytes
+    // agree.
+    return field_int_encoding<FieldType, StructT, Index>() == Encoding::Fixed;
   } else {
     return false;
   }
@@ -327,8 +327,7 @@ constexpr bool configurable_int_is_varint() {
   if constexpr (is_signed_configurable_int_v<FieldType>) {
     return field_int_encoding<FieldType, StructT, Index>() != Encoding::Fixed;
   } else if constexpr (is_unsigned_configurable_int_v<FieldType>) {
-    constexpr auto enc = field_int_encoding<FieldType, StructT, Index>();
-    return enc == Encoding::Varint || enc == Encoding::Tagged;
+    return field_int_encoding<FieldType, StructT, Index>() != Encoding::Fixed;
   } else {
     return false;
   }
@@ -366,16 +365,16 @@ constexpr size_t configurable_int_max_varint_bytes() {
     return 10;
   } else if constexpr (is_unsigned_configurable_int_v<FieldType>) {
     constexpr auto enc = field_int_encoding<FieldType, StructT, Index>();
-    if constexpr (enc == Encoding::Varint) {
-      if constexpr (is_configurable_int32_v<FieldType>) {
-        return 5;
-      }
-      return 10;
+    if constexpr (enc == Encoding::Fixed) {
+      return 0;
     }
     if constexpr (enc == Encoding::Tagged) {
       return 9;
     }
-    return 0;
+    if constexpr (is_configurable_int32_v<FieldType>) {
+      return 5;
+    }
+    return 10;
   } else {
     return 0;
   }
@@ -402,8 +401,13 @@ FORY_ALWAYS_INLINE uint32_t 
write_configurable_int_at(FieldType value,
     }
     return put_varint_at<FieldType>(value, buffer, offset);
   } else {
-    if constexpr (enc == Encoding::Varint) {
-      return put_varint_at<FieldType>(value, buffer, offset);
+    if constexpr (enc == Encoding::Fixed) {
+      if constexpr (is_configurable_int32_v<FieldType>) {
+        buffer.unsafe_put<uint32_t>(offset, static_cast<uint32_t>(value));
+        return 4;
+      }
+      buffer.unsafe_put<uint64_t>(offset, static_cast<uint64_t>(value));
+      return 8;
     }
     if constexpr (enc == Encoding::Tagged) {
       if constexpr (is_configurable_int64_v<FieldType>) {
@@ -411,12 +415,7 @@ FORY_ALWAYS_INLINE uint32_t 
write_configurable_int_at(FieldType value,
       }
       return put_varint_at<FieldType>(value, buffer, offset);
     }
-    if constexpr (is_configurable_int32_v<FieldType>) {
-      buffer.unsafe_put<uint32_t>(offset, static_cast<uint32_t>(value));
-      return 4;
-    }
-    buffer.unsafe_put<uint64_t>(offset, static_cast<uint64_t>(value));
-    return 8;
+    return put_varint_at<FieldType>(value, buffer, offset);
   }
 }
 
@@ -447,8 +446,17 @@ FORY_ALWAYS_INLINE FieldType 
read_configurable_int_at(Buffer &buffer,
     }
     return read_varint_at<FieldType>(buffer, offset);
   } else {
-    if constexpr (enc == Encoding::Varint) {
-      return read_varint_at<FieldType>(buffer, offset);
+    if constexpr (enc == Encoding::Fixed) {
+      if constexpr (is_configurable_int32_v<FieldType>) {
+        FieldType value =
+            static_cast<FieldType>(buffer.unsafe_get<uint32_t>(offset));
+        offset += 4;
+        return value;
+      }
+      FieldType value =
+          static_cast<FieldType>(buffer.unsafe_get<uint64_t>(offset));
+      offset += 8;
+      return value;
     }
     if constexpr (enc == Encoding::Tagged) {
       if constexpr (is_configurable_int64_v<FieldType>) {
@@ -459,16 +467,7 @@ FORY_ALWAYS_INLINE FieldType 
read_configurable_int_at(Buffer &buffer,
       }
       return read_varint_at<FieldType>(buffer, offset);
     }
-    if constexpr (is_configurable_int32_v<FieldType>) {
-      FieldType value =
-          static_cast<FieldType>(buffer.unsafe_get<uint32_t>(offset));
-      offset += 4;
-      return value;
-    }
-    FieldType value =
-        static_cast<FieldType>(buffer.unsafe_get<uint64_t>(offset));
-    offset += 8;
-    return value;
+    return read_varint_at<FieldType>(buffer, offset);
   }
 }
 
@@ -492,19 +491,19 @@ FORY_ALWAYS_INLINE FieldType 
read_configurable_int(ReadContext &ctx) {
     }
     return static_cast<FieldType>(ctx.read_var_int64(ctx.error()));
   } else {
-    if constexpr (enc == Encoding::Varint) {
+    if constexpr (enc == Encoding::Fixed) {
       if constexpr (is_configurable_int32_v<FieldType>) {
-        return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
+        return static_cast<FieldType>(ctx.read_int32(ctx.error()));
       }
-      return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
+      return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
     }
     if constexpr (enc == Encoding::Tagged) {
       return static_cast<FieldType>(ctx.read_tagged_uint64(ctx.error()));
     }
     if constexpr (is_configurable_int32_v<FieldType>) {
-      return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+      return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
     }
-    return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
+    return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
   }
 }
 
@@ -693,10 +692,10 @@ constexpr uint32_t configured_scalar_type_id() {
     return static_cast<uint32_t>(TypeId::VARINT32);
   } else if constexpr (std::is_same_v<Decayed, uint32_t> ||
                        std::is_same_v<Decayed, unsigned int>) {
-    if constexpr (enc == Encoding::Varint) {
-      return static_cast<uint32_t>(TypeId::VAR_UINT32);
+    if constexpr (enc == Encoding::Fixed) {
+      return static_cast<uint32_t>(TypeId::UINT32);
     }
-    return static_cast<uint32_t>(TypeId::UINT32);
+    return static_cast<uint32_t>(TypeId::VAR_UINT32);
   } else if constexpr (std::is_same_v<Decayed, int64_t> ||
                        std::is_same_v<Decayed, long long>) {
     if constexpr (enc == Encoding::Fixed) {
@@ -707,12 +706,12 @@ constexpr uint32_t configured_scalar_type_id() {
     return static_cast<uint32_t>(TypeId::VARINT64);
   } else if constexpr (std::is_same_v<Decayed, uint64_t> ||
                        std::is_same_v<Decayed, unsigned long long>) {
-    if constexpr (enc == Encoding::Varint) {
-      return static_cast<uint32_t>(TypeId::VAR_UINT64);
+    if constexpr (enc == Encoding::Fixed) {
+      return static_cast<uint32_t>(TypeId::UINT64);
     } else if constexpr (enc == Encoding::Tagged) {
       return static_cast<uint32_t>(TypeId::TAGGED_UINT64);
     }
-    return static_cast<uint32_t>(TypeId::UINT64);
+    return static_cast<uint32_t>(TypeId::VAR_UINT64);
   } else if constexpr (std::is_same_v<Decayed, float16_t>) {
     return static_cast<uint32_t>(TypeId::FLOAT16);
   } else if constexpr (std::is_same_v<Decayed, bfloat16_t>) {
@@ -773,18 +772,18 @@ FORY_ALWAYS_INLINE void write_configured_scalar(const 
FieldType &value,
       configured_node_encoding<StructT, Index, NodeIndex>();
   if constexpr (is_configurable_int_v<FieldType>) {
     if constexpr (std::is_same_v<FieldType, uint32_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        ctx.write_var_uint32(value);
-      } else {
+      if constexpr (enc == Encoding::Fixed) {
         ctx.buffer().write_int32(static_cast<int32_t>(value));
+      } else {
+        ctx.write_var_uint32(value);
       }
     } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        ctx.write_var_uint64(value);
+      if constexpr (enc == Encoding::Fixed) {
+        ctx.buffer().write_int64(static_cast<int64_t>(value));
       } else if constexpr (enc == Encoding::Tagged) {
         ctx.write_tagged_uint64(value);
       } else {
-        ctx.buffer().write_int64(static_cast<int64_t>(value));
+        ctx.write_var_uint64(value);
       }
     } else if constexpr (std::is_same_v<FieldType, int32_t> ||
                          std::is_same_v<FieldType, int>) {
@@ -816,17 +815,17 @@ FORY_ALWAYS_INLINE FieldType 
read_configured_scalar(ReadContext &ctx) {
     constexpr Encoding enc =
         configured_node_encoding<StructT, Index, NodeIndex>();
     if constexpr (std::is_same_v<FieldType, uint32_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
+      if constexpr (enc == Encoding::Fixed) {
+        return static_cast<FieldType>(ctx.read_int32(ctx.error()));
       }
-      return static_cast<FieldType>(ctx.read_int32(ctx.error()));
+      return static_cast<FieldType>(ctx.read_var_uint32(ctx.error()));
     } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
+      if constexpr (enc == Encoding::Fixed) {
+        return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
       } else if constexpr (enc == Encoding::Tagged) {
         return static_cast<FieldType>(ctx.read_tagged_uint64(ctx.error()));
       }
-      return static_cast<FieldType>(ctx.read_uint64(ctx.error()));
+      return static_cast<FieldType>(ctx.read_var_uint64(ctx.error()));
     } else if constexpr (std::is_same_v<FieldType, int32_t> ||
                          std::is_same_v<FieldType, int>) {
       if constexpr (enc == Encoding::Fixed) {
@@ -1463,8 +1462,8 @@ template <typename T> struct CompileTimeFieldHelpers {
   }
 
   /// Check if field at Index uses fixed-size encoding based on C++ type
-  /// Fixed types: bool, int8, uint8, int16, uint16, uint32, uint64, float,
-  /// double. Signed int32/int64 are fixed only when field encoding is
+  /// Fixed types: bool, int8, uint8, int16, uint16, float, double. 
Configurable
+  /// int32/int64/uint32/uint64 fields are fixed only when field encoding is
   /// configured as fixed.
   template <size_t Index> static constexpr bool field_is_fixed_primitive() {
     if constexpr (FieldCount == 0) {
@@ -1491,8 +1490,8 @@ template <typename T> struct CompileTimeFieldHelpers {
     }
   }
 
-  /// Check if field at Index uses varint encoding based on C++ type
-  /// Varint types: int32, int, int64, long long (signed integers use zigzag)
+  /// Check if field at Index uses varint/tagged encoding based on C++ type.
+  /// Configurable integer fields default to varint unless configured as fixed.
   template <size_t Index> static constexpr bool field_is_varint_primitive() {
     if constexpr (FieldCount == 0) {
       return false;
@@ -2565,19 +2564,19 @@ void write_single_field(const T &obj, WriteContext &ctx,
     using InnerType = typename std::remove_reference_t<FieldType>::value_type;
     InnerType value = field_value.value();
     if constexpr (std::is_same_v<InnerType, uint32_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        ctx.write_var_uint32(value);
-      } else {
+      if constexpr (enc == Encoding::Fixed) {
         ctx.buffer().write_int32(static_cast<int32_t>(value));
+      } else {
+        ctx.write_var_uint32(value);
       }
     } else if constexpr (std::is_same_v<InnerType, uint64_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        ctx.write_var_uint64(value);
+      if constexpr (enc == Encoding::Fixed) {
+        // For fixed encoding, cast to int64 since binary representation is 
same
+        ctx.buffer().write_int64(static_cast<int64_t>(value));
       } else if constexpr (enc == Encoding::Tagged) {
         ctx.write_tagged_uint64(value);
       } else {
-        // For fixed encoding, cast to int64 since binary representation is 
same
-        ctx.buffer().write_int64(static_cast<int64_t>(value));
+        ctx.write_var_uint64(value);
       }
     }
     return;
@@ -2626,19 +2625,19 @@ void write_single_field(const T &obj, WriteContext &ctx,
       constexpr auto enc =
           ::fory::detail::GetFieldConfigEntry<T, Index>::encoding;
       if constexpr (std::is_same_v<FieldType, uint32_t>) {
-        if constexpr (enc == Encoding::Varint) {
-          ctx.write_var_uint32(field_value);
-        } else {
+        if constexpr (enc == Encoding::Fixed) {
           ctx.buffer().write_int32(static_cast<int32_t>(field_value));
+        } else {
+          ctx.write_var_uint32(field_value);
         }
         return;
       } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
-        if constexpr (enc == Encoding::Varint) {
-          ctx.write_var_uint64(field_value);
+        if constexpr (enc == Encoding::Fixed) {
+          ctx.buffer().write_int64(static_cast<int64_t>(field_value));
         } else if constexpr (enc == Encoding::Tagged) {
           ctx.write_tagged_uint64(field_value);
         } else {
-          ctx.buffer().write_int64(static_cast<int64_t>(field_value));
+          ctx.write_var_uint64(field_value);
         }
         return;
       } else if constexpr (std::is_same_v<FieldType, int32_t> ||
@@ -2922,8 +2921,8 @@ FORY_ALWAYS_INLINE FieldType 
read_primitive_field_direct(ReadContext &ctx,
   static_assert(is_raw_primitive_v<FieldType>,
                 "read_primitive_field_direct only supports raw primitives");
 
-  // Use the actual C++ type, not TypeId, because default encoding differs
-  // between signed (varint) and unsigned (fixed) primitives.
+  // Use the actual C++ type, not TypeId. Fixed unsigned fields use the
+  // explicit fixed read helpers; this path follows serializer defaults.
   if constexpr (std::is_same_v<FieldType, bool>) {
     uint8_t v = ctx.read_uint8(error);
     return v != 0;
@@ -2942,14 +2941,12 @@ FORY_ALWAYS_INLINE FieldType 
read_primitive_field_direct(ReadContext &ctx,
     // int32_t uses varint encoding
     return ctx.read_var_int32(error);
   } else if constexpr (std::is_same_v<FieldType, uint32_t>) {
-    // uint32_t uses fixed 4-byte encoding (not varint!)
-    return static_cast<uint32_t>(ctx.read_int32(error));
+    return ctx.read_var_uint32(error);
   } else if constexpr (std::is_same_v<FieldType, int64_t>) {
     // int64_t uses varint encoding
     return ctx.read_var_int64(error);
   } else if constexpr (std::is_same_v<FieldType, uint64_t>) {
-    // uint64_t uses fixed 8-byte encoding (not varint!)
-    return static_cast<uint64_t>(ctx.read_int64(error));
+    return ctx.read_var_uint64(error);
   } else if constexpr (std::is_same_v<FieldType, float16_t>) {
     return ctx.read_f16(error);
   } else if constexpr (std::is_same_v<FieldType, bfloat16_t>) {
@@ -3066,18 +3063,18 @@ void read_single_field_by_index(T &obj, ReadContext 
&ctx) {
       using InnerType = typename 
std::remove_reference_t<FieldType>::value_type;
       InnerType value;
       if constexpr (std::is_same_v<InnerType, uint32_t>) {
-        if constexpr (enc == Encoding::Varint) {
-          value = ctx.read_var_uint32(ctx.error());
-        } else {
+        if constexpr (enc == Encoding::Fixed) {
           value = static_cast<uint32_t>(ctx.read_int32(ctx.error()));
+        } else {
+          value = ctx.read_var_uint32(ctx.error());
         }
       } else if constexpr (std::is_same_v<InnerType, uint64_t>) {
-        if constexpr (enc == Encoding::Varint) {
-          value = ctx.read_var_uint64(ctx.error());
+        if constexpr (enc == Encoding::Fixed) {
+          value = ctx.read_uint64(ctx.error());
         } else if constexpr (enc == Encoding::Tagged) {
           value = ctx.read_tagged_uint64(ctx.error());
         } else {
-          value = ctx.read_uint64(ctx.error());
+          value = ctx.read_var_uint64(ctx.error());
         }
       }
       field_value_set(obj, field_entry, std::optional<InnerType>(value));
@@ -3351,6 +3348,11 @@ template <typename T>
 FORY_ALWAYS_INLINE T read_primitive_at_checked(Buffer &buffer, uint32_t 
&offset,
                                                Error &error);
 
+template <typename FieldType, typename StructT, size_t Index>
+FORY_ALWAYS_INLINE FieldType read_configurable_int_at_checked(Buffer &buffer,
+                                                              uint32_t &offset,
+                                                              Error &error);
+
 template <typename T, size_t Index>
 constexpr bool can_read_exact_primitive_with_offset() {
   using Helpers = CompileTimeFieldHelpers<T>;
@@ -3378,8 +3380,17 @@ FORY_ALWAYS_INLINE void read_single_exact_primitive_at(T 
&obj, ReadContext &ctx,
   using RawFieldType =
       typename meta::RemoveMemberPointerCVRefT<decltype(field_ptr)>;
   using FieldType = unwrap_field_t<RawFieldType>;
-  FieldType value =
-      read_primitive_at_checked<FieldType>(ctx.buffer(), offset, ctx.error());
+  FieldType value;
+  if constexpr (is_configurable_int_v<FieldType>) {
+    // Compatible exact schema still needs the local field encoding config:
+    // uint32_t/uint64_t default to VAR_UINT*, while explicit fixed fields do
+    // not.
+    value = read_configurable_int_at_checked<FieldType, T, original_index>(
+        ctx.buffer(), offset, ctx.error());
+  } else {
+    value =
+        read_primitive_at_checked<FieldType>(ctx.buffer(), offset, 
ctx.error());
+  }
   if constexpr (is_fory_field_v<RawFieldType>) {
     (obj.*field_ptr).value = value;
   } else {
@@ -3911,6 +3922,44 @@ FORY_ALWAYS_INLINE bool ensure_offset_readable(Buffer 
&buffer, uint32_t offset,
   return true;
 }
 
+FORY_ALWAYS_INLINE uint64_t read_tagged_uint64_at_checked(Buffer &buffer,
+                                                          uint32_t &offset,
+                                                          Error &error) {
+  if (FORY_PREDICT_FALSE(!ensure_offset_readable<4>(buffer, offset, error))) {
+    return 0;
+  }
+  uint32_t first = buffer.unsafe_get<uint32_t>(offset);
+  if ((first & 0b1) != 0b1) {
+    offset += 4;
+    return static_cast<uint64_t>(first >> 1);
+  }
+  if (FORY_PREDICT_FALSE(!ensure_offset_readable<9>(buffer, offset, error))) {
+    return 0;
+  }
+  uint64_t value = buffer.unsafe_get<uint64_t>(offset + 1);
+  offset += 9;
+  return value;
+}
+
+FORY_ALWAYS_INLINE int64_t read_tagged_int64_at_checked(Buffer &buffer,
+                                                        uint32_t &offset,
+                                                        Error &error) {
+  if (FORY_PREDICT_FALSE(!ensure_offset_readable<4>(buffer, offset, error))) {
+    return 0;
+  }
+  int32_t first = buffer.unsafe_get<int32_t>(offset);
+  if ((first & 0b1) != 0b1) {
+    offset += 4;
+    return static_cast<int64_t>(first >> 1);
+  }
+  if (FORY_PREDICT_FALSE(!ensure_offset_readable<9>(buffer, offset, error))) {
+    return 0;
+  }
+  int64_t value = buffer.unsafe_get<int64_t>(offset + 1);
+  offset += 9;
+  return value;
+}
+
 template <typename T>
 FORY_ALWAYS_INLINE T read_fixed_primitive_at_checked(Buffer &buffer,
                                                      uint32_t &offset,
@@ -3950,6 +3999,13 @@ FORY_ALWAYS_INLINE T 
read_fixed_primitive_at_checked(Buffer &buffer,
     T value = buffer.unsafe_get<uint16_t>(offset);
     offset += 2;
     return value;
+  } else if constexpr (std::is_same_v<T, int32_t> || std::is_same_v<T, int>) {
+    if (FORY_PREDICT_FALSE(!ensure_offset_readable<4>(buffer, offset, error))) 
{
+      return T{};
+    }
+    T value = static_cast<T>(buffer.unsafe_get<int32_t>(offset));
+    offset += 4;
+    return value;
   } else if constexpr (std::is_same_v<T, uint32_t> ||
                        std::is_same_v<T, unsigned int>) {
     if (FORY_PREDICT_FALSE(!ensure_offset_readable<4>(buffer, offset, error))) 
{
@@ -3965,6 +4021,14 @@ FORY_ALWAYS_INLINE T 
read_fixed_primitive_at_checked(Buffer &buffer,
     T value = buffer.unsafe_get<float>(offset);
     offset += 4;
     return value;
+  } else if constexpr (std::is_same_v<T, int64_t> ||
+                       std::is_same_v<T, long long>) {
+    if (FORY_PREDICT_FALSE(!ensure_offset_readable<8>(buffer, offset, error))) 
{
+      return T{};
+    }
+    T value = static_cast<T>(buffer.unsafe_get<int64_t>(offset));
+    offset += 8;
+    return value;
   } else if constexpr (std::is_same_v<T, uint64_t> ||
                        std::is_same_v<T, unsigned long long>) {
     if (FORY_PREDICT_FALSE(!ensure_offset_readable<8>(buffer, offset, error))) 
{
@@ -4000,6 +4064,39 @@ FORY_ALWAYS_INLINE T 
read_fixed_primitive_at_checked(Buffer &buffer,
   }
 }
 
+template <typename FieldType, typename StructT, size_t Index>
+FORY_ALWAYS_INLINE FieldType read_configurable_int_at_checked(Buffer &buffer,
+                                                              uint32_t &offset,
+                                                              Error &error) {
+  static_assert(is_configurable_int_v<FieldType>,
+                "read_configurable_int_at_checked requires a configurable int "
+                "type");
+  constexpr Encoding enc = field_int_encoding<FieldType, StructT, Index>();
+  if constexpr (is_signed_configurable_int_v<FieldType>) {
+    if constexpr (enc == Encoding::Fixed) {
+      return read_fixed_primitive_at_checked<FieldType>(buffer, offset, error);
+    }
+    if constexpr (enc == Encoding::Tagged) {
+      if constexpr (is_configurable_int64_v<FieldType>) {
+        return static_cast<FieldType>(
+            read_tagged_int64_at_checked(buffer, offset, error));
+      }
+    }
+    return read_varint_at_checked<FieldType>(buffer, offset, error);
+  } else {
+    if constexpr (enc == Encoding::Fixed) {
+      return read_fixed_primitive_at_checked<FieldType>(buffer, offset, error);
+    }
+    if constexpr (enc == Encoding::Tagged) {
+      if constexpr (is_configurable_int64_v<FieldType>) {
+        return static_cast<FieldType>(
+            read_tagged_uint64_at_checked(buffer, offset, error));
+      }
+    }
+    return read_varint_at_checked<FieldType>(buffer, offset, error);
+  }
+}
+
 template <typename T>
 FORY_ALWAYS_INLINE T read_primitive_at_checked(Buffer &buffer, uint32_t 
&offset,
                                                Error &error) {
diff --git a/cpp/fory/serialization/struct_test.cc 
b/cpp/fory/serialization/struct_test.cc
index 0d2ba527d..bdf9d8854 100644
--- a/cpp/fory/serialization/struct_test.cc
+++ b/cpp/fory/serialization/struct_test.cc
@@ -261,6 +261,52 @@ struct AllPrimitivesStruct {
               float_val, double_val);
 };
 
+struct UnsignedDefaultEncodingWriter {
+  uint32_t u32;
+  uint64_t u64;
+
+  bool operator==(const UnsignedDefaultEncodingWriter &other) const {
+    return u32 == other.u32 && u64 == other.u64;
+  }
+
+  FORY_STRUCT(UnsignedDefaultEncodingWriter, u32, u64);
+};
+
+struct UnsignedExplicitVarintReader {
+  uint32_t u32 = 0;
+  uint64_t u64 = 0;
+
+  FORY_STRUCT(UnsignedExplicitVarintReader, (u32, fory::F().varint()),
+              (u64, fory::F().varint()));
+};
+
+struct UnsignedEncodingStruct {
+  uint32_t default_u32;
+  uint64_t default_u64;
+  uint32_t id_default_u32;
+  uint64_t id_default_u64;
+  uint32_t var_u32;
+  uint32_t fixed_u32;
+  uint64_t var_u64;
+  uint64_t fixed_u64;
+  uint64_t tagged_u64;
+
+  bool operator==(const UnsignedEncodingStruct &other) const {
+    return default_u32 == other.default_u32 &&
+           default_u64 == other.default_u64 &&
+           id_default_u32 == other.id_default_u32 &&
+           id_default_u64 == other.id_default_u64 && var_u32 == other.var_u32 
&&
+           fixed_u32 == other.fixed_u32 && var_u64 == other.var_u64 &&
+           fixed_u64 == other.fixed_u64 && tagged_u64 == other.tagged_u64;
+  }
+
+  FORY_STRUCT(UnsignedEncodingStruct, default_u32, default_u64,
+              (id_default_u32, fory::F(1)), (id_default_u64, fory::F(2)),
+              (var_u32, fory::F().varint()), (fixed_u32, fory::F().fixed()),
+              (var_u64, fory::F().varint()), (fixed_u64, fory::F().fixed()),
+              (tagged_u64, fory::F().tagged()));
+};
+
 // String handling
 struct StringTestStruct {
   std::string empty;
@@ -608,6 +654,9 @@ inline void register_all_test_types(Fory &fory) {
   fory.register_struct<PimplPropertyStruct>(type_id++);
   fory.register_struct<PimplConfiguredPropertyStruct>(type_id++);
   fory.register_struct<AllPrimitivesStruct>(type_id++);
+  fory.register_struct<UnsignedDefaultEncodingWriter>(type_id++);
+  fory.register_struct<UnsignedExplicitVarintReader>(type_id++);
+  fory.register_struct<UnsignedEncodingStruct>(type_id++);
   fory.register_struct<StringTestStruct>(type_id++);
   fory.register_struct<Point2D>(type_id++);
   fory.register_struct<Point3D>(type_id++);
@@ -724,6 +773,131 @@ TEST(StructComprehensiveTest, AllPrimitivesMin) {
                                      -DBL_MAX});
 }
 
+TEST(StructComprehensiveTest, UnsignedDefaultEncodingRoundTrip) {
+  auto writer =
+      Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+  auto reader =
+      Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+  ASSERT_TRUE(writer.register_struct<UnsignedDefaultEncodingWriter>(610).ok());
+  ASSERT_TRUE(reader.register_struct<UnsignedExplicitVarintReader>(610).ok());
+
+  UnsignedDefaultEncodingWriter original{66051u, 0x01020304050607ULL};
+  auto bytes_result = writer.serialize(original);
+  ASSERT_TRUE(bytes_result.ok())
+      << "Serialization failed: " << bytes_result.error().to_string();
+
+  std::vector<uint8_t> bytes = std::move(bytes_result).value();
+  auto result = reader.deserialize<UnsignedExplicitVarintReader>(bytes.data(),
+                                                                 bytes.size());
+  ASSERT_TRUE(result.ok()) << "Deserialization failed: "
+                           << result.error().to_string();
+  EXPECT_EQ(result.value().u32, original.u32);
+  EXPECT_EQ(result.value().u64, original.u64);
+
+  auto explicit_bytes = reader.serialize(
+      UnsignedExplicitVarintReader{original.u32, original.u64});
+  ASSERT_TRUE(explicit_bytes.ok())
+      << "Serialization failed: " << explicit_bytes.error().to_string();
+  auto default_result = writer.deserialize<UnsignedDefaultEncodingWriter>(
+      explicit_bytes.value().data(), explicit_bytes.value().size());
+  ASSERT_TRUE(default_result.ok())
+      << "Deserialization failed: " << default_result.error().to_string();
+  EXPECT_EQ(default_result.value(), original);
+}
+
+TEST(StructComprehensiveTest, UnsignedEncodingsRoundTrip) {
+  test_roundtrip(UnsignedEncodingStruct{
+      66051u,
+      0x01020304050607ULL,
+      131071u,
+      0x02030405060708ULL,
+      262143u,
+      0x03020100u,
+      0x03040506070809ULL,
+      0x0405060708090aULL,
+      0x05060708090a0bULL,
+  });
+}
+
+TEST(StructComprehensiveTest, UnsignedEncodingFieldMeta) {
+  auto fory =
+      Fory::builder().xlang(true).compatible(true).track_ref(false).build();
+  ASSERT_TRUE(fory.register_struct<UnsignedEncodingStruct>(611).ok());
+  ASSERT_TRUE(fory.serialize(UnsignedEncodingStruct{
+                                 1u,
+                                 2u,
+                                 3u,
+                                 4u,
+                                 5u,
+                                 6u,
+                                 7u,
+                                 8u,
+                                 9u,
+                             })
+                  .ok());
+
+  TypeMeta meta =
+      fory.type_resolver().clone_struct_meta<UnsignedEncodingStruct>();
+  const auto &fields = meta.get_field_infos();
+  ASSERT_EQ(fields.size(), 9);
+
+  auto find_field = [&](const std::string &name) -> const FieldInfo * {
+    auto it =
+        std::find_if(fields.begin(), fields.end(), [&](const FieldInfo &field) 
{
+          return field.field_name == name;
+        });
+    return it == fields.end() ? nullptr : &*it;
+  };
+  auto find_field_id = [&](int16_t id) -> const FieldInfo * {
+    auto it =
+        std::find_if(fields.begin(), fields.end(), [&](const FieldInfo &field) 
{
+          return field.field_id == id;
+        });
+    return it == fields.end() ? nullptr : &*it;
+  };
+
+  const FieldInfo *default_u32 = find_field("default_u32");
+  const FieldInfo *default_u64 = find_field("default_u64");
+  const FieldInfo *id_default_u32 = find_field_id(1);
+  const FieldInfo *id_default_u64 = find_field_id(2);
+  const FieldInfo *var_u32 = find_field("var_u32");
+  const FieldInfo *fixed_u32 = find_field("fixed_u32");
+  const FieldInfo *var_u64 = find_field("var_u64");
+  const FieldInfo *fixed_u64 = find_field("fixed_u64");
+  const FieldInfo *tagged_u64 = find_field("tagged_u64");
+
+  ASSERT_NE(default_u32, nullptr);
+  ASSERT_NE(default_u64, nullptr);
+  ASSERT_NE(id_default_u32, nullptr);
+  ASSERT_NE(id_default_u64, nullptr);
+  ASSERT_NE(var_u32, nullptr);
+  ASSERT_NE(fixed_u32, nullptr);
+  ASSERT_NE(var_u64, nullptr);
+  ASSERT_NE(fixed_u64, nullptr);
+  ASSERT_NE(tagged_u64, nullptr);
+
+  EXPECT_EQ(default_u32->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT32));
+  EXPECT_EQ(default_u64->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT64));
+  EXPECT_EQ(id_default_u32->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT32));
+  EXPECT_EQ(id_default_u32->field_id, 1);
+  EXPECT_EQ(id_default_u64->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT64));
+  EXPECT_EQ(id_default_u64->field_id, 2);
+  EXPECT_EQ(var_u32->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT32));
+  EXPECT_EQ(fixed_u32->field_type.type_id,
+            static_cast<uint32_t>(TypeId::UINT32));
+  EXPECT_EQ(var_u64->field_type.type_id,
+            static_cast<uint32_t>(TypeId::VAR_UINT64));
+  EXPECT_EQ(fixed_u64->field_type.type_id,
+            static_cast<uint32_t>(TypeId::UINT64));
+  EXPECT_EQ(tagged_u64->field_type.type_id,
+            static_cast<uint32_t>(TypeId::TAGGED_UINT64));
+}
+
 TEST(StructComprehensiveTest, StringVariations) {
   test_roundtrip(StringTestStruct{"", "Hello", "UTF-8", "Short"});
 
diff --git a/cpp/fory/serialization/type_resolver.h 
b/cpp/fory/serialization/type_resolver.h
index 5e6ec218b..dcf358b13 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -634,13 +634,13 @@ void apply_integer_encoding(FieldType &ft, const 
FieldNodeSpec &spec,
     ft.set_type_id(static_cast<uint32_t>(TypeId::UINT16));
   } else if constexpr (std::is_same_v<Decayed, uint32_t>) {
     ft.set_type_id(static_cast<uint32_t>(
-        enc == Encoding::Varint ? TypeId::VAR_UINT32 : TypeId::UINT32));
+        enc == Encoding::Fixed ? TypeId::UINT32 : TypeId::VAR_UINT32));
   } else if constexpr (std::is_same_v<Decayed, uint64_t>) {
-    ft.set_type_id(static_cast<uint32_t>(enc == Encoding::Varint
-                                             ? TypeId::VAR_UINT64
+    ft.set_type_id(static_cast<uint32_t>(enc == Encoding::Fixed
+                                             ? TypeId::UINT64
                                              : (enc == Encoding::Tagged
                                                     ? TypeId::TAGGED_UINT64
-                                                    : TypeId::UINT64)));
+                                                    : TypeId::VAR_UINT64)));
   } else if constexpr (std::is_same_v<Decayed, int32_t> ||
                        std::is_same_v<Decayed, int>) {
     ft.set_type_id(static_cast<uint32_t>(
@@ -1118,19 +1118,19 @@ constexpr uint32_t compute_unsigned_type_id() {
     } else if constexpr (std::is_same_v<InnerType, uint16_t>) {
       return static_cast<uint32_t>(TypeId::UINT16);
     } else if constexpr (std::is_same_v<InnerType, uint32_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        return static_cast<uint32_t>(TypeId::VAR_UINT32);
-      } else {
+      // uint32_t defaults to Serializer<uint32_t>::type_id (VAR_UINT32);
+      // only an explicit fixed field config should emit UINT32 metadata.
+      if constexpr (enc == Encoding::Fixed) {
         return static_cast<uint32_t>(TypeId::UINT32);
       }
+      return static_cast<uint32_t>(TypeId::VAR_UINT32);
     } else if constexpr (std::is_same_v<InnerType, uint64_t>) {
-      if constexpr (enc == Encoding::Varint) {
-        return static_cast<uint32_t>(TypeId::VAR_UINT64);
+      if constexpr (enc == Encoding::Fixed) {
+        return static_cast<uint32_t>(TypeId::UINT64);
       } else if constexpr (enc == Encoding::Tagged) {
         return static_cast<uint32_t>(TypeId::TAGGED_UINT64);
-      } else {
-        return static_cast<uint32_t>(TypeId::UINT64);
       }
+      return static_cast<uint32_t>(TypeId::VAR_UINT64);
     }
   }
   // Not an unsigned type with configured encoding; use the type default.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to