Commit 95be334d authored by Kenton Varda's avatar Kenton Varda

Update implemented pointer format to match docs: Don't store field count in…

Update implemented pointer format to match docs:  Don't store field count in struct pointers because it isn't needed.  Instead extend data and pointer section sizes to 16 bits, which should be enough for anyone.  Also, store offsets from the end of the pointer rather than the beginning, because this makes it possible for them to be zero, which compresses better.  In particular, the root reference will always have a zero offset, so this will shave a byte off of very small packed messages.
parent 8ac9494c
......@@ -98,7 +98,7 @@ TEST(Encoding, DefaultInitializationMultiSegment) {
}
TEST(Encoding, DefaultsFromEmptyMessage) {
AlignedData<1> emptyMessage = {{4, 0, 0, 0, 0, 0, 0, 0}};
AlignedData<1> emptyMessage = {{0, 0, 0, 0, 0, 0, 0, 0}};
ArrayPtr<const word> segments[1] = {arrayPtr(emptyMessage.words, 1)};
SegmentArrayMessageReader reader(arrayPtr(segments, 1));
......
......@@ -39,8 +39,8 @@ namespace {
TEST(WireFormat, SimpleRawDataStruct) {
AlignedData<2> data = {{
// Struct ref, offset = 1, fieldCount = 1, dataSize = 1, referenceCount = 0
0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
// Struct ref, offset = 1, dataSize = 1, referenceCount = 0
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
// Content for the data segment.
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
}};
......@@ -81,28 +81,15 @@ TEST(WireFormat, SimpleRawDataStruct) {
EXPECT_TRUE (reader.getDataField<bool>(63 * ELEMENTS, true ));
EXPECT_FALSE(reader.getDataField<bool>(64 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataField<bool>(64 * ELEMENTS, true ));
// Field number guards.
EXPECT_EQ(0xefcdab89u,
reader.getDataFieldCheckingNumber<uint32_t>(FieldNumber(0), 1 * ELEMENTS, 321u));
EXPECT_EQ(321u,
reader.getDataFieldCheckingNumber<uint32_t>(FieldNumber(1), 1 * ELEMENTS, 321u));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 0 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 0 * ELEMENTS, true ));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 1 * ELEMENTS, false));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(0), 1 * ELEMENTS, true ));
EXPECT_FALSE(reader.getDataFieldCheckingNumber<bool>(FieldNumber(1), 0 * ELEMENTS, false));
EXPECT_TRUE (reader.getDataFieldCheckingNumber<bool>(FieldNumber(1), 0 * ELEMENTS, true ));
}
static const AlignedData<2> STRUCT_DEFAULT = {{8,0,0,0,16,2,4,0, 0}};
static const AlignedData<6> STRUCT_DEFAULT = {{0,0,0,0,2,0,4,0, 0}};
static const AlignedData<2> SUBSTRUCT_DEFAULT = {{8,0,0,0,1,1,0,0, 0,0,0,0,0,0,0,0}};
static const AlignedData<2> SUBSTRUCT_DEFAULT = {{0,0,0,0,1,0,0,0, 0,0,0,0,0,0,0,0}};
static const AlignedData<3> STRUCTLIST_ELEMENT_DEFAULT =
{{8,0,0,0,1,1,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}};
{{0,0,0,0,1,0,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0}};
static const AlignedData<2> STRUCTLIST_ELEMENT_SUBSTRUCT_DEFAULT =
{{8,0,0,0,1,1,0,0, 0,0,0,0,0,0,0,0}};
{{0,0,0,0,1,0,0,0, 0,0,0,0,0,0,0,0}};
static void setupStruct(StructBuilder builder) {
builder.setDataField<uint64_t>(0 * ELEMENTS, 0x1011121314151617ull);
......
......@@ -45,7 +45,9 @@ struct WireReference {
//
// Actually this is not terribly common. The "offset" could actually be different things
// depending on the context:
// - For a regular (e.g. struct/list) reference, a signed word offset from the reference pointer.
// - For a regular (e.g. struct/list) reference, a signed word offset from the word immediately
// following the reference pointer. (The off-by-one means the offset is more often zero, saving
// bytes on the wire when packed.)
// - For an inline composite list tag (not really a reference, but structured similarly), an
// element count.
// - For a FAR reference, an unsigned offset into the target segment.
......@@ -70,19 +72,19 @@ struct WireReference {
WireValue<uint32_t> offsetAndKind;
CAPNPROTO_ALWAYS_INLINE(bool isNull() const) { return offsetAndKind.get() == 0; }
CAPNPROTO_ALWAYS_INLINE(Kind kind() const) {
return static_cast<Kind>(offsetAndKind.get() & 3);
}
CAPNPROTO_ALWAYS_INLINE(word* target()) {
return reinterpret_cast<word*>(this) + (static_cast<int32_t>(offsetAndKind.get()) >> 2);
return reinterpret_cast<word*>(this) + 1 + (static_cast<int32_t>(offsetAndKind.get()) >> 2);
}
CAPNPROTO_ALWAYS_INLINE(const word* target() const) {
return reinterpret_cast<const word*>(this) + (static_cast<int32_t>(offsetAndKind.get()) >> 2);
return reinterpret_cast<const word*>(this) + 1 +
(static_cast<int32_t>(offsetAndKind.get()) >> 2);
}
CAPNPROTO_ALWAYS_INLINE(void setKindAndTarget(Kind kind, word* target)) {
offsetAndKind.set(((target - reinterpret_cast<word*>(this)) << 2) | kind);
offsetAndKind.set(((target - reinterpret_cast<word*>(this) - 1) << 2) | kind);
}
CAPNPROTO_ALWAYS_INLINE(ElementCount inlineCompositeListElementCount() const) {
......@@ -113,21 +115,19 @@ struct WireReference {
// Part of reference that depends on the kind.
union {
uint32_t upper32Bits;
struct {
WireValue<FieldNumber> fieldCount;
WireValue<WordCount8> dataSize;
WireValue<WireReferenceCount8> refCount;
WireValue<uint8_t> reserved0;
WireValue<WordCount16> dataSize;
WireValue<WireReferenceCount16> refCount;
inline WordCount wordSize() const {
return dataSize.get() + refCount.get() * WORDS_PER_REFERENCE;
}
CAPNPROTO_ALWAYS_INLINE(void set(FieldNumber fc, WordCount ds, WireReferenceCount rc)) {
fieldCount.set(fc);
CAPNPROTO_ALWAYS_INLINE(void set(WordCount ds, WireReferenceCount rc)) {
dataSize.set(ds);
refCount.set(rc);
reserved0.set(0);
}
} structRef;
// Also covers capabilities.
......@@ -167,6 +167,14 @@ struct WireReference {
}
} farRef;
};
CAPNPROTO_ALWAYS_INLINE(bool isNull() const) {
// If the upper 32 bits are zero, this is a pointer to an empty struct. We consider that to be
// our "null" value.
// TODO: Maybe this would be faster, but violates aliasing rules; does it matter?:
// return *reinterpret_cast<const uint64_t*>(this) == 0;
return (offsetAndKind.get() == 0) & (upper32Bits == 0);
}
};
static_assert(sizeof(WireReference) == sizeof(word),
"capnproto::WireReference is not exactly one word. This will probably break everything.");
......@@ -314,8 +322,7 @@ struct WireHelpers {
copyStruct(segment, dstPtr, srcPtr, src->structRef.dataSize.get(),
src->structRef.refCount.get());
dst->structRef.set(src->structRef.fieldCount.get(), src->structRef.dataSize.get(),
src->structRef.refCount.get());
dst->structRef.set(src->structRef.dataSize.get(), src->structRef.refCount.get());
return dstPtr;
}
}
......@@ -408,8 +415,7 @@ struct WireHelpers {
defaultRef->structRef.dataSize.get() * BYTES_PER_WORD / BYTES);
// Initialize the reference.
ref->structRef.set(defaultRef->structRef.fieldCount.get(), defaultRef->structRef.dataSize.get(),
defaultRef->structRef.refCount.get());
ref->structRef.set(defaultRef->structRef.dataSize.get(), defaultRef->structRef.refCount.get());
// Build the StructBuilder.
return StructBuilder(segment, ptr,
......@@ -428,9 +434,6 @@ struct WireHelpers {
CAPNPROTO_DEBUG_ASSERT(ref->kind() == WireReference::STRUCT,
"Called getStruct{Field,Element}() but existing reference is not a struct.");
CAPNPROTO_DEBUG_ASSERT(
ref->structRef.fieldCount.get() == defaultRef->structRef.fieldCount.get(),
"Trying to update struct with incorrect field count.");
CAPNPROTO_DEBUG_ASSERT(
ref->structRef.dataSize.get() == defaultRef->structRef.dataSize.get(),
"Trying to update struct with incorrect data size.");
......@@ -482,7 +485,6 @@ struct WireHelpers {
reinterpret_cast<WireReference*>(ptr)->setKindAndInlineCompositeListElementCount(
WireReference::STRUCT, elementCount);
reinterpret_cast<WireReference*>(ptr)->structRef.set(
defaultRef->structRef.fieldCount.get(),
defaultRef->structRef.dataSize.get(),
defaultRef->structRef.refCount.get());
ptr += REFERENCE_SIZE_IN_WORDS;
......@@ -651,7 +653,6 @@ struct WireHelpers {
return StructReader(
segment, ptr, reinterpret_cast<const WireReference*>(ptr + ref->structRef.dataSize.get()),
ref->structRef.fieldCount.get(),
ref->structRef.dataSize.get(),
ref->structRef.refCount.get(),
0 * BITS, nestingLimit - 1);
......@@ -776,10 +777,7 @@ struct WireHelpers {
}
return ListReader(segment, ptr, size, wordsPerElement * BITS_PER_WORD,
tag->structRef.fieldCount.get(),
tag->structRef.dataSize.get(),
tag->structRef.refCount.get(),
nestingLimit - 1);
tag->structRef.dataSize.get(), tag->structRef.refCount.get(), nestingLimit - 1);
} else {
// The elements of the list are NOT structs.
......@@ -830,7 +828,7 @@ struct WireHelpers {
break;
}
return ListReader(segment, ptr, ref->listRef.elementCount(), step, FieldNumber(1),
return ListReader(segment, ptr, ref->listRef.elementCount(), step,
dataSize, referenceCount, nestingLimit - 1);
} else {
CAPNPROTO_ASSERT(segment != nullptr, "Trusted message had incompatible list element type.");
......@@ -1008,17 +1006,14 @@ Data::Builder StructBuilder::getDataField(
}
StructReader StructBuilder::asReader() const {
// HACK: We just give maxed-out field, data size, and reference counts because they are only
// HACK: We just give maxed-out data size and reference counts because they are only
// used for checking for field presence.
static_assert(sizeof(WireReference::structRef.fieldCount) == 1,
"Has the maximum field count changed?");
static_assert(sizeof(WireReference::structRef.dataSize) == 1,
static_assert(sizeof(WireReference::structRef.dataSize) == 2,
"Has the maximum data size changed?");
static_assert(sizeof(WireReference::structRef.refCount) == 1,
static_assert(sizeof(WireReference::structRef.refCount) == 2,
"Has the maximum reference count changed?");
return StructReader(segment, data, references,
FieldNumber(0xff), 0xff * WORDS, 0xff * REFERENCES,
0 * BITS, std::numeric_limits<int>::max());
0xffff * WORDS, 0xffff * REFERENCES, 0 * BITS, std::numeric_limits<int>::max());
}
StructReader StructReader::readRootTrusted(const word* location, const word* defaultValue) {
......@@ -1122,11 +1117,10 @@ ListReader ListBuilder::asReader(FieldSize elementSize) const {
std::numeric_limits<int>::max());
}
ListReader ListBuilder::asReader(FieldNumber fieldCount, WordCount dataSize,
WireReferenceCount referenceCount) const {
ListReader ListBuilder::asReader(WordCount dataSize, WireReferenceCount referenceCount) const {
return ListReader(segment, ptr, elementCount,
(dataSize + referenceCount * WORDS_PER_REFERENCE) * BITS_PER_WORD / ELEMENTS,
fieldCount, dataSize, referenceCount, std::numeric_limits<int>::max());
dataSize, referenceCount, std::numeric_limits<int>::max());
}
StructReader ListReader::getStructElement(ElementCount index, const word* defaultValue) const {
......@@ -1140,8 +1134,7 @@ StructReader ListReader::getStructElement(ElementCount index, const word* defaul
return StructReader(
segment, structPtr,
reinterpret_cast<const WireReference*>(structPtr + structDataSize * BYTES_PER_WORD),
structFieldCount, structDataSize, structReferenceCount, indexBit % BITS_PER_BYTE,
nestingLimit - 1);
structDataSize, structReferenceCount, indexBit % BITS_PER_BYTE, nestingLimit - 1);
}
}
......
......@@ -231,7 +231,7 @@ private:
class StructReader {
public:
inline StructReader()
: segment(nullptr), data(nullptr), references(nullptr), fieldCount(0), dataSize(0),
: segment(nullptr), data(nullptr), references(nullptr), dataSize(0),
referenceCount(0), bit0Offset(0 * BITS), nestingLimit(0) {}
static StructReader readRootTrusted(const word* location, const word* defaultValue);
......@@ -245,14 +245,6 @@ public:
// multiples of the field size, determined by the type. Returns the default value if the offset
// is past the end of the struct's data segment.
template <typename T>
CAPNPROTO_ALWAYS_INLINE(T getDataFieldCheckingNumber(
FieldNumber fieldNumber, ElementCount offset, typename NoInfer<T>::Type defaultValue) const);
// Like getDataField() but also returns the default if the field number not less than the field
// count. This is needed in cases where the field was packed into a hole preceding other fields
// with later numbers, and therefore the offset being in-bounds alone does not prove that the
// struct contains the field.
StructReader getStructField(WireReferenceCount refIndex, const word* defaultValue) const;
// Get the struct field at the given index in the reference segment, or the default value if not
// initialized. defaultValue will be interpreted as a trusted message -- it must point at a
......@@ -277,7 +269,6 @@ private:
const void* data;
const WireReference* references;
FieldNumber fieldCount; // Number of fields the struct is reported to have.
WordCount8 dataSize; // Size of data segment.
WireReferenceCount8 referenceCount; // Size of the reference segment.
......@@ -291,9 +282,9 @@ private:
// Once this reaches zero, further pointers will be pruned.
inline StructReader(SegmentReader* segment, const void* data, const WireReference* references,
FieldNumber fieldCount, WordCount dataSize, WireReferenceCount referenceCount,
WordCount dataSize, WireReferenceCount referenceCount,
BitCount bit0Offset, int nestingLimit)
: segment(segment), data(data), references(references), fieldCount(fieldCount),
: segment(segment), data(data), references(references),
dataSize(dataSize), referenceCount(referenceCount), bit0Offset(bit0Offset),
nestingLimit(nestingLimit) {}
......@@ -358,8 +349,7 @@ public:
ListReader asReader(FieldSize elementSize) const;
// Get a ListReader pointing at the same memory. Use this version only for non-struct lists.
ListReader asReader(FieldNumber fieldCount, WordCount dataSize,
WireReferenceCount referenceCount) const;
ListReader asReader(WordCount dataSize, WireReferenceCount referenceCount) const;
// Get a ListReader pointing at the same memory. Use this version only for struct lists.
private:
......@@ -378,7 +368,7 @@ class ListReader {
public:
inline ListReader()
: segment(nullptr), ptr(nullptr), elementCount(0),
stepBits(0 * BITS / ELEMENTS), structFieldCount(0), structDataSize(0),
stepBits(0 * BITS / ELEMENTS), structDataSize(0),
structReferenceCount(0), nestingLimit(0) {}
inline ElementCount size();
......@@ -414,10 +404,9 @@ private:
// if the sender upgraded a data list to a struct list. It will always be aligned properly for
// the type. Unsigned so that division by a constant power of 2 is efficient.
FieldNumber structFieldCount;
WordCount structDataSize;
WireReferenceCount structReferenceCount;
// If the elements are structs, the properties of the struct. The field and reference counts are
// If the elements are structs, the properties of the struct. The reference count is
// only used to check for field presence; the data size is also used to compute the reference
// pointer.
......@@ -428,15 +417,14 @@ private:
inline ListReader(SegmentReader* segment, const void* ptr, ElementCount elementCount,
decltype(BITS / ELEMENTS) stepBits, int nestingLimit)
: segment(segment), ptr(ptr), elementCount(elementCount), stepBits(stepBits),
structFieldCount(0), structDataSize(0), structReferenceCount(0),
structDataSize(0), structReferenceCount(0),
nestingLimit(nestingLimit) {}
inline ListReader(SegmentReader* segment, const void* ptr, ElementCount elementCount,
decltype(BITS / ELEMENTS) stepBits,
FieldNumber structFieldCount, WordCount structDataSize,
decltype(BITS / ELEMENTS) stepBits, WordCount structDataSize,
WireReferenceCount structReferenceCount, int nestingLimit)
: segment(segment), ptr(ptr), elementCount(elementCount), stepBits(stepBits),
structFieldCount(structFieldCount), structDataSize(structDataSize),
structReferenceCount(structReferenceCount), nestingLimit(nestingLimit) {}
structDataSize(structDataSize), structReferenceCount(structReferenceCount),
nestingLimit(nestingLimit) {}
friend class StructReader;
friend class ListBuilder;
......@@ -512,41 +500,6 @@ inline Void StructReader::getDataField<Void>(ElementCount offset, Void defaultVa
return Void::VOID;
}
template <typename T>
T StructReader::getDataFieldCheckingNumber(
FieldNumber fieldNumber, ElementCount offset, typename NoInfer<T>::Type defaultValue) const {
// Intentionally use & rather than && to reduce branches.
if ((fieldNumber < fieldCount) &
(offset * bytesPerElement<T>() < dataSize * BYTES_PER_WORD)) {
return reinterpret_cast<const WireValue<T>*>(data)[offset / ELEMENTS].get();
} else {
return defaultValue;
}
}
template <>
inline bool StructReader::getDataFieldCheckingNumber<bool>(
FieldNumber fieldNumber, ElementCount offset, bool defaultValue) const {
BitCount boffset = offset * (1 * BITS / ELEMENTS);
// This branch should always be optimized away when inlining.
if (boffset == 0 * BITS) boffset = bit0Offset;
// Intentionally use & rather than && to reduce branches.
if ((fieldNumber < fieldCount) & (boffset < dataSize * BITS_PER_WORD)) {
const byte* b = reinterpret_cast<const byte*>(data) + boffset / BITS_PER_BYTE;
return (*reinterpret_cast<const uint8_t*>(b) & (1 << (boffset % BITS_PER_BYTE / BITS))) != 0;
} else {
return defaultValue;
}
}
template <>
inline Void StructReader::getDataFieldCheckingNumber<Void>(
FieldNumber fieldNumber, ElementCount offset, Void defaultValue) const {
return Void::VOID;
}
// -------------------------------------------------------------------
inline ElementCount ListBuilder::size() { return elementCount; }
......
......@@ -388,8 +388,10 @@ TEST(Packed, RoundTripAllZero) {
PackedMessageReader reader(pipe);
checkTestMessageAllZero(reader.getRoot<TestAllTypes>());
// Most of the bytes are the segment table and root reference.
EXPECT_LE(pipe.getData().size(), 9u);
// Segment table packs to 2 bytes.
// Root reference packs to 3 bytes.
// Content packs to 2 bytes (zero span).
EXPECT_LE(pipe.getData().size(), 7u);
}
TEST(Packed, RoundTripAllZeroScratchSpace) {
......
......@@ -100,6 +100,9 @@ Array<word> messageToFlatArray(ArrayPtr<const ArrayPtr<const word>> segments) {
internal::WireValue<uint32_t>* table =
reinterpret_cast<internal::WireValue<uint32_t>*>(result.begin());
// We write the segment count - 1 because this makes the first word zero for single-segment
// messages, improving compression. We don't bother doing this with segment sizes because
// one-word segments are rare anyway.
table[0].set(segments.size() - 1);
for (uint i = 0; i < segments.size(); i++) {
......@@ -219,6 +222,9 @@ void writeMessage(OutputStream& output, ArrayPtr<const ArrayPtr<const word>> seg
internal::WireValue<uint32_t> table[(segments.size() + 2) & ~size_t(1)];
// We write the segment count - 1 because this makes the first word zero for single-segment
// messages, improving compression. We don't bother doing this with segment sizes because
// one-word segments are rare anyway.
table[0].set(segments.size() - 1);
for (uint i = 0; i < segments.size(); i++) {
table[i + 1].set(segments[i].size());
......
......@@ -93,7 +93,7 @@ encodeData size = loop 0 where
popBits _ rest = ([], rest)
encodeReferences :: Integer -> Integer -> [(Integer, TypeDesc, ValueDesc)] -> ([Word8], [Word8])
encodeReferences o size = loop 0 (o + size) where
encodeReferences o size = loop 0 (o + size - 1) where
loop idx offset ((pos, t, v):rest) | idx == pos = let
(ref, obj) = case (t, v) of
(StructType desc, StructValueDesc assignments) -> let
......@@ -134,10 +134,8 @@ encodeStructList o desc elements = loop (o + eSize * genericLength elements) ele
encodeStructReference desc offset =
bytes (offset * 4 + structTag) 4 ++
[ fromIntegral (length (structFields desc) + length (structUnions desc))
, fromIntegral $ packingDataSize $ structPacking desc
, fromIntegral $ packingReferenceCount $ structPacking desc
, 0 ]
bytes (packingDataSize $ structPacking desc) 2 ++
bytes (packingReferenceCount $ structPacking desc) 2
encodeListReference elemSize@(SizeInlineComposite ds rc) elementCount offset =
bytes (offset * 4 + listTag) 4 ++
......@@ -234,8 +232,8 @@ encodeList elementType elements = case elementSize elementType of
encodeMessage (StructType desc) (StructValueDesc assignments) = let
(dataBytes, refBytes, childBytes) = encodeStruct desc assignments 0
in concat [encodeStructReference desc (1::Integer), dataBytes, refBytes, childBytes]
in concat [encodeStructReference desc (0::Integer), dataBytes, refBytes, childBytes]
encodeMessage (ListType elementType) (ListDesc elements) =
encodeListReference (elementSize elementType) (genericLength elements) (1::Integer) ++
encodeListReference (elementSize elementType) (genericLength elements) (0::Integer) ++
encodeList elementType elements
encodeMessage _ _ = error "Not a message."
......@@ -77,7 +77,7 @@ A list value is encoded as a pointer to a flat array of values.
+-+-----------------------------+--+----------------------------+
A (2 bits) = 1, to indicate that this is a list pointer.
B (30 bits) = Offset, in words, from the start of the pointer to the
B (30 bits) = Offset, in words, from the end of the pointer to the
start of the first element of the list. Signed.
C (3 bits) = Size of each element:
0 = 0 (e.g. List(Void))
......@@ -123,7 +123,7 @@ A struct pointer looks like this:
+-+-----------------------------+---------------+---------------+
A (2 bits) = 0, to indicate that this is a struct pointer.
B (30 bits) = Offset, in words, from the start of the pointer to the
B (30 bits) = Offset, in words, from the end of the pointer to the
start of the struct's data section. Signed.
C (16 bits) = Size of the struct's data section, in words.
D (16 bits) = Size of the struct's pointer section, in words.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment