Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ This is the entry point for AI guidance in Apache Fory. Read this file first, th
- Preserve protocol compatibility across languages.
- Read and respect `docs/specification/xlang_type_mapping.md` when changing cross-language type behavior.
- Handle byte order correctly for cross-platform compatibility.
- If the reference implementation is not right, do not tweak another language's correct implementation to align with a wrong reference implementation just to make tests pass; fix the runtime that diverged from the spec.

## Git And Review Rules

Expand Down
1 change: 1 addition & 0 deletions cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The C++ implementation provides high-performance serialization with compile-time
- **Type-Safe**: Compile-time type checking with template specialization
- **Shared References**: Automatic tracking of shared and circular references
- **Schema Evolution**: Compatible mode for independent schema changes
- **Reduced-Precision Types**: `fory::float16_t` and `fory::bfloat16_t` scalars with dense `std::vector<...>` array carriers
- **Two Formats**: Object graph serialization and zero-copy row-based format
- **Modern C++17**: Clean API using modern C++ features

Expand Down
113 changes: 113 additions & 0 deletions cpp/fory/serialization/array_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -402,5 +402,118 @@ template <size_t N> struct Serializer<std::array<float16_t, N>> {
}
};

/// Serializer for std::array<bfloat16_t, N>
template <size_t N> struct Serializer<std::array<bfloat16_t, N>> {
static constexpr TypeId type_id = TypeId::BFLOAT16_ARRAY;

static inline void write_type_info(WriteContext &ctx) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}

static inline void read_type_info(ReadContext &ctx) {
uint32_t actual = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return;
}
if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) {
ctx.set_error(
Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
}
}

static inline void write(const std::array<bfloat16_t, N> &arr,
WriteContext &ctx, RefMode ref_mode, bool write_type,
bool has_generics = false) {
write_not_null_ref_flag(ctx, ref_mode);
if (write_type) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}
write_data(arr, ctx);
}

static inline void write_data(const std::array<bfloat16_t, N> &arr,
WriteContext &ctx) {
Buffer &buffer = ctx.buffer();
constexpr size_t max_size = 8 + N * sizeof(bfloat16_t);
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index += buffer.put_var_uint32(
writer_index, static_cast<uint32_t>(N * sizeof(bfloat16_t)));
if constexpr (N > 0) {
if constexpr (FORY_LITTLE_ENDIAN) {
buffer.unsafe_put(writer_index, arr.data(), N * sizeof(bfloat16_t));
} else {
for (size_t i = 0; i < N; ++i) {
uint16_t bits = util::to_little_endian(arr[i].to_bits());
buffer.unsafe_put(writer_index + i * sizeof(bfloat16_t), &bits,
sizeof(bfloat16_t));
}
}
}
buffer.writer_index(writer_index + N * sizeof(bfloat16_t));
}

static inline void write_data_generic(const std::array<bfloat16_t, N> &arr,
WriteContext &ctx, bool has_generics) {
write_data(arr, ctx);
}

static inline std::array<bfloat16_t, N>
read(ReadContext &ctx, RefMode ref_mode, bool read_type) {
bool has_value = read_null_only_flag(ctx, ref_mode);
if (ctx.has_error() || !has_value) {
return std::array<bfloat16_t, N>();
}
if (read_type) {
uint32_t type_id_read = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return std::array<bfloat16_t, N>();
}
if (type_id_read != static_cast<uint32_t>(type_id)) {
ctx.set_error(
Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id)));
return std::array<bfloat16_t, N>();
}
}
return read_data(ctx);
}

static inline std::array<bfloat16_t, N> read_data(ReadContext &ctx) {
uint32_t size_bytes = ctx.read_var_uint32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return std::array<bfloat16_t, N>();
}
uint32_t length = size_bytes / sizeof(bfloat16_t);
if (length != N) {
ctx.set_error(Error::invalid_data("Array size mismatch: expected " +
std::to_string(N) + " but got " +
std::to_string(length)));
return std::array<bfloat16_t, N>();
}
std::array<bfloat16_t, N> arr;
if constexpr (N > 0) {
if constexpr (FORY_LITTLE_ENDIAN) {
ctx.read_bytes(arr.data(), N * sizeof(bfloat16_t), ctx.error());
} else {
for (size_t i = 0; i < N; ++i) {
uint16_t bits;
ctx.read_bytes(&bits, sizeof(bfloat16_t), ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return arr;
}
arr[i] = bfloat16_t::from_bits(util::to_little_endian(bits));
}
}
}
return arr;
}

static inline std::array<bfloat16_t, N>
read_with_type_info(ReadContext &ctx, RefMode ref_mode,
const TypeInfo &type_info) {
return read(ctx, ref_mode, false);
}
};

} // namespace serialization
} // namespace fory
72 changes: 72 additions & 0 deletions cpp/fory/serialization/basic_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "fory/serialization/context.h"
#include "fory/serialization/serializer_traits.h"
#include "fory/type/type.h"
#include "fory/util/bfloat16.h"
#include "fory/util/error.h"
#include "fory/util/float16.h"
#include <cstdint>
Expand Down Expand Up @@ -603,6 +604,77 @@ template <> struct Serializer<float16_t> {
}
};

/// bfloat16_t serializer
template <> struct Serializer<bfloat16_t> {
static constexpr TypeId type_id = TypeId::BFLOAT16;

static inline void write_type_info(WriteContext &ctx) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}

static inline void read_type_info(ReadContext &ctx) {
uint32_t actual = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return;
}
if (actual != static_cast<uint32_t>(type_id)) {
ctx.set_error(
Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
}
}

static inline void write(bfloat16_t value, WriteContext &ctx,
RefMode ref_mode, bool write_type, bool = false) {
write_not_null_ref_flag(ctx, ref_mode);
if (write_type) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}
write_data(value, ctx);
}

static inline void write_data(bfloat16_t value, WriteContext &ctx) {
ctx.write_bytes(&value, sizeof(bfloat16_t));
}

static inline void write_data_generic(bfloat16_t value, WriteContext &ctx,
bool) {
write_data(value, ctx);
}

static inline bfloat16_t read(ReadContext &ctx, RefMode ref_mode,
bool read_type) {
bool has_value = read_null_only_flag(ctx, ref_mode);
if (ctx.has_error() || !has_value) {
return bfloat16_t::from_bits(0);
}
if (read_type) {
uint32_t type_id_read = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return bfloat16_t::from_bits(0);
}
if (type_id_read != static_cast<uint32_t>(type_id)) {
ctx.set_error(
Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id)));
return bfloat16_t::from_bits(0);
}
}
return ctx.read_bf16(ctx.error());
}

static inline bfloat16_t read_data(ReadContext &ctx) {
return ctx.read_bf16(ctx.error());
}

static inline bfloat16_t read_data_generic(ReadContext &ctx, bool) {
return read_data(ctx);
}

static inline bfloat16_t
read_with_type_info(ReadContext &ctx, RefMode ref_mode, const TypeInfo &) {
return read(ctx, ref_mode, false);
}
};

// ============================================================================
// Character Type Serializers (C++ native only, not supported in xlang mode)
// ============================================================================
Expand Down
106 changes: 106 additions & 0 deletions cpp/fory/serialization/collection_serializer.h
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,112 @@ template <typename Alloc> struct Serializer<std::vector<float16_t, Alloc>> {
}
};

/// Vector serializer for bfloat16_t — typed array path (BFLOAT16_ARRAY).
template <typename Alloc> struct Serializer<std::vector<bfloat16_t, Alloc>> {
static constexpr TypeId type_id = TypeId::BFLOAT16_ARRAY;

static inline void write_type_info(WriteContext &ctx) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}

static inline void read_type_info(ReadContext &ctx) {
uint32_t actual = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return;
}
if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) {
ctx.set_error(
Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
}
}

static inline void write(const std::vector<bfloat16_t, Alloc> &vec,
WriteContext &ctx, RefMode ref_mode, bool write_type,
bool has_generics = false) {
write_not_null_ref_flag(ctx, ref_mode);
if (write_type) {
ctx.write_uint8(static_cast<uint8_t>(type_id));
}
write_data(vec, ctx);
}

static inline void write_data(const std::vector<bfloat16_t, Alloc> &vec,
WriteContext &ctx) {
uint64_t total_bytes =
static_cast<uint64_t>(vec.size()) * sizeof(bfloat16_t);
if (total_bytes > std::numeric_limits<uint32_t>::max()) {
ctx.set_error(Error::invalid("Vector byte size exceeds uint32_t range"));
return;
}
Buffer &buffer = ctx.buffer();
size_t max_size = 8 + total_bytes;
buffer.grow(static_cast<uint32_t>(max_size));
uint32_t writer_index = buffer.writer_index();
writer_index +=
buffer.put_var_uint32(writer_index, static_cast<uint32_t>(total_bytes));
if (total_bytes > 0) {
buffer.unsafe_put(writer_index, vec.data(),
static_cast<uint32_t>(total_bytes));
}
buffer.writer_index(writer_index + static_cast<uint32_t>(total_bytes));
}

static inline void
write_data_generic(const std::vector<bfloat16_t, Alloc> &vec,
WriteContext &ctx, bool has_generics) {
write_data(vec, ctx);
}

static inline std::vector<bfloat16_t, Alloc>
read(ReadContext &ctx, RefMode ref_mode, bool read_type) {
bool has_value = read_null_only_flag(ctx, ref_mode);
if (ctx.has_error() || !has_value) {
return std::vector<bfloat16_t, Alloc>();
}
if (read_type) {
uint32_t type_id_read = ctx.read_uint8(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return std::vector<bfloat16_t, Alloc>();
}
if (type_id_read != static_cast<uint32_t>(type_id)) {
ctx.set_error(
Error::type_mismatch(type_id_read, static_cast<uint32_t>(type_id)));
return std::vector<bfloat16_t, Alloc>();
}
}
return read_data(ctx);
}

static inline std::vector<bfloat16_t, Alloc>
read_with_type_info(ReadContext &ctx, RefMode ref_mode,
const TypeInfo &type_info) {
return read(ctx, ref_mode, false);
}

static inline std::vector<bfloat16_t, Alloc> read_data(ReadContext &ctx) {
uint32_t total_bytes_u32 = ctx.read_var_uint32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return std::vector<bfloat16_t, Alloc>();
}
if (FORY_PREDICT_FALSE(total_bytes_u32 > ctx.config().max_binary_size)) {
ctx.set_error(Error::invalid_data("Binary size exceeds max_binary_size"));
return std::vector<bfloat16_t, Alloc>();
}
size_t elem_count = total_bytes_u32 / sizeof(bfloat16_t);
if (total_bytes_u32 % sizeof(bfloat16_t) != 0) {
ctx.set_error(Error::invalid_data(
"Vector byte size not aligned with bfloat16_t element size"));
return std::vector<bfloat16_t, Alloc>();
}
std::vector<bfloat16_t, Alloc> result(elem_count);
if (total_bytes_u32 > 0) {
ctx.read_bytes(result.data(), static_cast<uint32_t>(total_bytes_u32),
ctx.error());
}
return result;
}
};

/// Vector serializer for non-bool, non-arithmetic types
template <typename T, typename Alloc>
struct Serializer<
Expand Down
5 changes: 5 additions & 0 deletions cpp/fory/serialization/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,11 @@ class ReadContext {
return buffer().read_f16(error);
}

/// Read bfloat16_t from buffer. Sets error on failure.
FORY_ALWAYS_INLINE bfloat16_t read_bf16(Error &error) {
return buffer().read_bf16(error);
}

/// Read uint32_t value as varint from buffer. Sets error on failure.
FORY_ALWAYS_INLINE uint32_t read_var_uint32(Error &error) {
return buffer().read_var_uint32(error);
Expand Down
26 changes: 3 additions & 23 deletions cpp/fory/serialization/skip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -630,33 +630,13 @@ void skip_field_value(ReadContext &ctx, const FieldType &field_type,
return;
}
}
// Read array length
uint32_t len = ctx.read_var_uint32(ctx.error());
// Typed primitive arrays encode payload size in bytes, not element count.
uint32_t payload_size = ctx.read_var_uint32(ctx.error());
if (FORY_PREDICT_FALSE(ctx.has_error())) {
return;
}

// Calculate element size
size_t elem_size = 1;
switch (tid) {
case TypeId::INT16_ARRAY:
case TypeId::FLOAT16_ARRAY:
case TypeId::BFLOAT16_ARRAY:
elem_size = 2;
break;
case TypeId::INT32_ARRAY:
case TypeId::FLOAT32_ARRAY:
elem_size = 4;
break;
case TypeId::INT64_ARRAY:
case TypeId::FLOAT64_ARRAY:
elem_size = 8;
break;
default:
break;
}

ctx.buffer().increase_reader_index(len * elem_size, ctx.error());
ctx.buffer().increase_reader_index(payload_size, ctx.error());
return;
}

Expand Down
Loading
Loading