From 198733a1af834c01b05b6c55fe0206f648ca99f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6ppe?= Date: Mon, 23 Mar 2026 16:31:58 +0000 Subject: [PATCH] [flexbuffers] Add "AlignedBlob", a version of "Blob" with explicit alignment. A blob is an array of bytes and has no intrinsic alignment (i.e. the alignment is 1). The alignment of the existing flexbuffers blob is solely affected by the width of the integer needed to store the blob's size: that integer's width becomes the alignment of the blob. The proposed AlignedBlob function here piggybacks on this effect and simply uses a user-defined alignment for the width of the integer that stores the blob's size; this automatically imparts that same alignment on the blob itself. (The width is bounded below by the actual width needed to store the blob's size.) The ability to control the alignment of a blob is important for use cases in which the blob itself stores structured data that we want to access without further copies (e.g. other flatbuffer messages). --- include/flatbuffers/flexbuffers.h | 20 ++++++++++++++++--- tests/flexbuffers_test.cpp | 33 +++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/include/flatbuffers/flexbuffers.h b/include/flatbuffers/flexbuffers.h index 2784dafbfa..1ed6a41bca 100644 --- a/include/flatbuffers/flexbuffers.h +++ b/include/flatbuffers/flexbuffers.h @@ -1207,11 +1207,20 @@ class Builder FLATBUFFERS_FINAL_CLASS { String(str); } + size_t AlignedBlob(const void* data, size_t len, BitWidth alignment) { + // The requested alignment must not be smaller than the one required to + // store the length. + return CreateAlignedBlob(data, len, 0, FBT_BLOB, + std::max(alignment, WidthU(len))); + } + size_t AlignedBlob(const std::vector& v, BitWidth alignment) { + return AlignedBlob(v.data(), v.size(), alignment); + } size_t Blob(const void* data, size_t len) { return CreateBlob(data, len, 0, FBT_BLOB); } size_t Blob(const std::vector& v) { - return CreateBlob(v.data(), v.size(), 0, FBT_BLOB); + return Blob(v.data(), v.size()); } void Blob(const char* key, const void* data, size_t len) { @@ -1693,11 +1702,16 @@ class Builder FLATBUFFERS_FINAL_CLASS { size_t CreateBlob(const void* data, size_t len, size_t trailing, Type type) { auto bit_width = WidthU(len); - auto byte_width = Align(bit_width); + return CreateAlignedBlob(data, len, trailing, type, bit_width); + } + + size_t CreateAlignedBlob(const void* data, size_t len, size_t trailing, + Type type, BitWidth alignment) { + auto byte_width = Align(alignment); Write(len, byte_width); auto sloc = buf_.size(); WriteBytes(data, len + trailing); - stack_.push_back(Value(static_cast(sloc), type, bit_width)); + stack_.push_back(Value(static_cast(sloc), type, alignment)); return sloc; } diff --git a/tests/flexbuffers_test.cpp b/tests/flexbuffers_test.cpp index 3c87f8c713..6087a0affb 100644 --- a/tests/flexbuffers_test.cpp +++ b/tests/flexbuffers_test.cpp @@ -1,5 +1,6 @@ #include "flexbuffers_test.h" +#include #include #include "flatbuffers/flexbuffers.h" @@ -13,6 +14,13 @@ namespace tests { // Shortcuts for the infinity. static const auto infinity_d = std::numeric_limits::infinity(); +static bool IsAligned(const void* ptr, std::size_t alignment) { + void* p = const_cast(ptr); + std::size_t space = 2 * alignment; + void* q = std::align(alignment, alignment, p, space); + return q != nullptr && p == ptr && space == 2 * alignment; +} + void FlexBuffersTest() { flexbuffers::Builder slb(512, flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS); @@ -29,7 +37,10 @@ void FlexBuffersTest() { slb.IndirectFloat(4.0f); auto i_f = slb.LastValue(); uint8_t blob[] = {77}; - slb.Blob(blob, 1); + uint32_t aligned_blob[] = {88, 99}; + slb.Blob(blob, sizeof blob); + slb.AlignedBlob(aligned_blob, sizeof aligned_blob, + flexbuffers::BIT_WIDTH_32); slb += false; slb.ReuseValue(i_f); }); @@ -62,7 +73,7 @@ void FlexBuffersTest() { auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap(); TEST_EQ(map.size(), 7); auto vec = map["vec"].AsVector(); - TEST_EQ(vec.size(), 6); + TEST_EQ(vec.size(), 7); TEST_EQ(vec[0].AsInt64(), -100); TEST_EQ_STR(vec[1].AsString().c_str(), "Fred"); TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed. @@ -80,9 +91,15 @@ void FlexBuffersTest() { auto blob = vec[3].AsBlob(); TEST_EQ(blob.size(), 1); TEST_EQ(blob.data()[0], 77); - TEST_EQ(vec[4].IsBool(), true); // Check if type is a bool - TEST_EQ(vec[4].AsBool(), false); // Check if value is false - TEST_EQ(vec[5].AsDouble(), 4.0); // This is shared with vec[2] ! + TEST_EQ(vec[4].IsBlob(), true); + auto aligned_blob = vec[4].AsBlob(); + TEST_EQ(aligned_blob.size(), 8); + TEST_EQ(reinterpret_cast(aligned_blob.data())[0], 88); + TEST_EQ(reinterpret_cast(aligned_blob.data())[1], 99); + TEST_EQ(IsAligned(aligned_blob.data(), 4), true); + TEST_EQ(vec[5].IsBool(), true); // Check if type is a bool + TEST_EQ(vec[5].AsBool(), false); // Check if value is false + TEST_EQ(vec[6].AsDouble(), 4.0); // This is shared with vec[2] ! auto tvec = map["bar"].AsTypedVector(); TEST_EQ(tvec.size(), 3); TEST_EQ(tvec[2].AsInt8(), 3); @@ -107,9 +124,9 @@ void FlexBuffersTest() { TEST_EQ(vec[2].MutateFloat(2.0f), true); TEST_EQ(vec[2].AsFloat(), 2.0f); TEST_EQ(vec[2].MutateFloat(3.14159), false); // Double does not fit in float. - TEST_EQ(vec[4].AsBool(), false); // Is false before change - TEST_EQ(vec[4].MutateBool(true), true); // Can change a bool - TEST_EQ(vec[4].AsBool(), true); // Changed bool is now true + TEST_EQ(vec[5].AsBool(), false); // Is false before change + TEST_EQ(vec[5].MutateBool(true), true); // Can change a bool + TEST_EQ(vec[5].AsBool(), true); // Changed bool is now true // Parse from JSON: flatbuffers::Parser parser;