[flexbuffers] Add "AlignedBlob", a version of "Blob" with explicit alignment. (#8993)

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).
This commit is contained in:
Thomas Köppe
2026-03-23 17:28:03 +00:00
committed by GitHub
parent 8396e00dd8
commit 4e582b0c1d
2 changed files with 42 additions and 11 deletions

View File

@@ -1207,11 +1207,20 @@ class Builder FLATBUFFERS_FINAL_CLASS {
String(str); 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<uint8_t>& v, BitWidth alignment) {
return AlignedBlob(v.data(), v.size(), alignment);
}
size_t Blob(const void* data, size_t len) { size_t Blob(const void* data, size_t len) {
return CreateBlob(data, len, 0, FBT_BLOB); return CreateBlob(data, len, 0, FBT_BLOB);
} }
size_t Blob(const std::vector<uint8_t>& v) { size_t Blob(const std::vector<uint8_t>& 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) { 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) { size_t CreateBlob(const void* data, size_t len, size_t trailing, Type type) {
auto bit_width = WidthU(len); 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<uint64_t>(len, byte_width); Write<uint64_t>(len, byte_width);
auto sloc = buf_.size(); auto sloc = buf_.size();
WriteBytes(data, len + trailing); WriteBytes(data, len + trailing);
stack_.push_back(Value(static_cast<uint64_t>(sloc), type, bit_width)); stack_.push_back(Value(static_cast<uint64_t>(sloc), type, alignment));
return sloc; return sloc;
} }

View File

@@ -1,5 +1,6 @@
#include "flexbuffers_test.h" #include "flexbuffers_test.h"
#include <memory>
#include <limits> #include <limits>
#include "flatbuffers/flexbuffers.h" #include "flatbuffers/flexbuffers.h"
@@ -13,6 +14,13 @@ namespace tests {
// Shortcuts for the infinity. // Shortcuts for the infinity.
static const auto infinity_d = std::numeric_limits<double>::infinity(); static const auto infinity_d = std::numeric_limits<double>::infinity();
static bool IsAligned(const void* ptr, std::size_t alignment) {
void* p = const_cast<void*>(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() { void FlexBuffersTest() {
flexbuffers::Builder slb(512, flexbuffers::Builder slb(512,
flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS); flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS);
@@ -29,7 +37,10 @@ void FlexBuffersTest() {
slb.IndirectFloat(4.0f); slb.IndirectFloat(4.0f);
auto i_f = slb.LastValue(); auto i_f = slb.LastValue();
uint8_t blob[] = {77}; 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 += false;
slb.ReuseValue(i_f); slb.ReuseValue(i_f);
}); });
@@ -62,7 +73,7 @@ void FlexBuffersTest() {
auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap(); auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap();
TEST_EQ(map.size(), 7); TEST_EQ(map.size(), 7);
auto vec = map["vec"].AsVector(); auto vec = map["vec"].AsVector();
TEST_EQ(vec.size(), 6); TEST_EQ(vec.size(), 7);
TEST_EQ(vec[0].AsInt64(), -100); TEST_EQ(vec[0].AsInt64(), -100);
TEST_EQ_STR(vec[1].AsString().c_str(), "Fred"); TEST_EQ_STR(vec[1].AsString().c_str(), "Fred");
TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed. TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed.
@@ -80,9 +91,15 @@ void FlexBuffersTest() {
auto blob = vec[3].AsBlob(); auto blob = vec[3].AsBlob();
TEST_EQ(blob.size(), 1); TEST_EQ(blob.size(), 1);
TEST_EQ(blob.data()[0], 77); TEST_EQ(blob.data()[0], 77);
TEST_EQ(vec[4].IsBool(), true); // Check if type is a bool TEST_EQ(vec[4].IsBlob(), true);
TEST_EQ(vec[4].AsBool(), false); // Check if value is false auto aligned_blob = vec[4].AsBlob();
TEST_EQ(vec[5].AsDouble(), 4.0); // This is shared with vec[2] ! TEST_EQ(aligned_blob.size(), 8);
TEST_EQ(reinterpret_cast<const uint32_t*>(aligned_blob.data())[0], 88);
TEST_EQ(reinterpret_cast<const uint32_t*>(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(); auto tvec = map["bar"].AsTypedVector();
TEST_EQ(tvec.size(), 3); TEST_EQ(tvec.size(), 3);
TEST_EQ(tvec[2].AsInt8(), 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].MutateFloat(2.0f), true);
TEST_EQ(vec[2].AsFloat(), 2.0f); TEST_EQ(vec[2].AsFloat(), 2.0f);
TEST_EQ(vec[2].MutateFloat(3.14159), false); // Double does not fit in float. 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[5].AsBool(), false); // Is false before change
TEST_EQ(vec[4].MutateBool(true), true); // Can change a bool TEST_EQ(vec[5].MutateBool(true), true); // Can change a bool
TEST_EQ(vec[4].AsBool(), true); // Changed bool is now true TEST_EQ(vec[5].AsBool(), true); // Changed bool is now true
// Parse from JSON: // Parse from JSON:
flatbuffers::Parser parser; flatbuffers::Parser parser;