Commit 5ab5e2a3 authored by Kenton Varda's avatar Kenton Varda

Restore ability to upgrade from List(Bool) to List(T) where T is a struct whose…

Restore ability to upgrade from List(Bool) to List(T) where T is a struct whose @0 field is of type Bool.  I previously disallowed this to reduce complexity, but it turned out to actually increase complexity.
parent 9e3eb675
...@@ -435,7 +435,8 @@ TEST(Encoding, SmallStructLists) { ...@@ -435,7 +435,8 @@ TEST(Encoding, SmallStructLists) {
EXPECT_EQ(0u, sl.getStructListList().size()); EXPECT_EQ(0u, sl.getStructListList().size());
{ auto l = sl.initList0 (2); l[0].setF(Void::VOID); l[1].setF(Void::VOID); } { auto l = sl.initList0 (2); l[0].setF(Void::VOID); l[1].setF(Void::VOID); }
{ auto l = sl.initList1 (2); l[0].setF(true); l[1].setF(false); } { auto l = sl.initList1 (4); l[0].setF(true); l[1].setF(false);
l[2].setF(true); l[3].setF(true); }
{ auto l = sl.initList8 (2); l[0].setF(123u); l[1].setF(45u); } { auto l = sl.initList8 (2); l[0].setF(123u); l[1].setF(45u); }
{ auto l = sl.initList16(2); l[0].setF(12345u); l[1].setF(6789u); } { auto l = sl.initList16(2); l[0].setF(12345u); l[1].setF(6789u); }
{ auto l = sl.initList32(2); l[0].setF(123456789u); l[1].setF(234567890u); } { auto l = sl.initList32(2); l[0].setF(123456789u); l[1].setF(234567890u); }
...@@ -482,6 +483,139 @@ TEST(Encoding, SmallStructLists) { ...@@ -482,6 +483,139 @@ TEST(Encoding, SmallStructLists) {
} }
} }
// =======================================================================================
TEST(Encoding, ListUpgrade) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
root.initObjectField<List<uint16_t>>(3).copyFrom({12, 34, 56});
checkList(root.getObjectField<List<uint8_t>>(), {12, 34, 56});
{
auto l = root.getObjectField<List<test::TestLists::Struct8>>();
ASSERT_EQ(3u, l.size());
EXPECT_EQ(12u, l[0].getF());
EXPECT_EQ(34u, l[1].getF());
EXPECT_EQ(56u, l[2].getF());
}
checkList(root.getObjectField<List<uint16_t>>(), {12, 34, 56});
auto reader = root.asReader();
checkList(reader.getObjectField<List<uint8_t>>(), {12, 34, 56});
{
auto l = reader.getObjectField<List<test::TestLists::Struct8>>();
ASSERT_EQ(3u, l.size());
EXPECT_EQ(12u, l[0].getF());
EXPECT_EQ(34u, l[1].getF());
EXPECT_EQ(56u, l[2].getF());
}
try {
reader.getObjectField<List<uint32_t>>();
ADD_FAILURE() << "Expected exception.";
} catch (const Exception& e) {
// expected
}
{
auto l = reader.getObjectField<List<test::TestLists::Struct32>>();
ASSERT_EQ(3u, l.size());
// These should return default values because the structs aren't big enough.
EXPECT_EQ(0u, l[0].getF());
EXPECT_EQ(0u, l[1].getF());
EXPECT_EQ(0u, l[2].getF());
}
checkList(reader.getObjectField<List<uint16_t>>(), {12, 34, 56});
}
TEST(Encoding, BitListDowngrade) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
root.initObjectField<List<uint16_t>>(4).copyFrom({0x1201u, 0x3400u, 0x5601u, 0x7801u});
checkList(root.getObjectField<List<bool>>(), {true, false, true, true});
{
auto l = root.getObjectField<List<test::TestLists::Struct1>>();
ASSERT_EQ(4u, l.size());
EXPECT_TRUE(l[0].getF());
EXPECT_FALSE(l[1].getF());
EXPECT_TRUE(l[2].getF());
EXPECT_TRUE(l[3].getF());
}
checkList(root.getObjectField<List<uint16_t>>(), {0x1201u, 0x3400u, 0x5601u, 0x7801u});
auto reader = root.asReader();
checkList(reader.getObjectField<List<bool>>(), {true, false, true, true});
{
auto l = reader.getObjectField<List<test::TestLists::Struct1>>();
ASSERT_EQ(4u, l.size());
EXPECT_TRUE(l[0].getF());
EXPECT_FALSE(l[1].getF());
EXPECT_TRUE(l[2].getF());
EXPECT_TRUE(l[3].getF());
}
checkList(reader.getObjectField<List<uint16_t>>(), {0x1201u, 0x3400u, 0x5601u, 0x7801u});
}
TEST(Encoding, BitListUpgrade) {
MallocMessageBuilder builder;
auto root = builder.initRoot<test::TestObject>();
root.initObjectField<List<bool>>(4).copyFrom({true, false, true, true});
{
auto l = root.getObjectField<List<test::TestFieldZeroIsBit>>();
ASSERT_EQ(4u, l.size());
EXPECT_TRUE(l[0].getBit());
EXPECT_FALSE(l[1].getBit());
EXPECT_TRUE(l[2].getBit());
EXPECT_TRUE(l[3].getBit());
}
auto reader = root.asReader();
try {
reader.getObjectField<List<uint8_t>>();
ADD_FAILURE() << "Expected exception.";
} catch (const Exception& e) {
// expected
}
{
auto l = reader.getObjectField<List<test::TestFieldZeroIsBit>>();
ASSERT_EQ(4u, l.size());
EXPECT_TRUE(l[0].getBit());
EXPECT_FALSE(l[1].getBit());
EXPECT_TRUE(l[2].getBit());
EXPECT_TRUE(l[3].getBit());
// Other fields are defaulted.
EXPECT_TRUE(l[0].getSecondBit());
EXPECT_TRUE(l[1].getSecondBit());
EXPECT_TRUE(l[2].getSecondBit());
EXPECT_TRUE(l[3].getSecondBit());
EXPECT_EQ(123u, l[0].getThirdField());
EXPECT_EQ(123u, l[1].getThirdField());
EXPECT_EQ(123u, l[2].getThirdField());
EXPECT_EQ(123u, l[3].getThirdField());
}
checkList(reader.getObjectField<List<bool>>(), {true, false, true, true});
}
// ======================================================================================= // =======================================================================================
// Tests of generated code, not really of the encoding. // Tests of generated code, not really of the encoding.
// TODO(cleanup): Move to a different test? // TODO(cleanup): Move to a different test?
......
This diff is collapsed.
This diff is collapsed.
...@@ -53,7 +53,7 @@ internal::StructReader MessageReader::getRootInternal() { ...@@ -53,7 +53,7 @@ internal::StructReader MessageReader::getRootInternal() {
VALIDATE_INPUT(segment != nullptr && VALIDATE_INPUT(segment != nullptr &&
segment->containsInterval(segment->getStartPtr(), segment->getStartPtr() + 1), segment->containsInterval(segment->getStartPtr(), segment->getStartPtr() + 1),
"Message did not contain a root pointer.") { "Message did not contain a root pointer.") {
return internal::StructReader::readEmpty(); return internal::StructReader();
} }
return internal::StructReader::readRoot(segment->getStartPtr(), segment, options.nestingLimit); return internal::StructReader::readRoot(segment->getStartPtr(), segment, options.nestingLimit);
......
...@@ -329,7 +329,7 @@ void genericInitListDefaults(Builder builder) { ...@@ -329,7 +329,7 @@ void genericInitListDefaults(Builder builder) {
auto lists = builder.initLists(); auto lists = builder.initLists();
lists.initList0(2); lists.initList0(2);
lists.initList1(2); lists.initList1(4);
lists.initList8(2); lists.initList8(2);
lists.initList16(2); lists.initList16(2);
lists.initList32(2); lists.initList32(2);
...@@ -340,6 +340,8 @@ void genericInitListDefaults(Builder builder) { ...@@ -340,6 +340,8 @@ void genericInitListDefaults(Builder builder) {
lists.getList0()[1].setF(Void::VOID); lists.getList0()[1].setF(Void::VOID);
lists.getList1()[0].setF(true); lists.getList1()[0].setF(true);
lists.getList1()[1].setF(false); lists.getList1()[1].setF(false);
lists.getList1()[2].setF(true);
lists.getList1()[3].setF(true);
lists.getList8()[0].setF(123u); lists.getList8()[0].setF(123u);
lists.getList8()[1].setF(45u); lists.getList8()[1].setF(45u);
lists.getList16()[0].setF(12345u); lists.getList16()[0].setF(12345u);
...@@ -380,7 +382,7 @@ void genericCheckListDefaults(Reader reader) { ...@@ -380,7 +382,7 @@ void genericCheckListDefaults(Reader reader) {
auto lists = reader.getLists(); auto lists = reader.getLists();
ASSERT_EQ(2u, lists.getList0().size()); ASSERT_EQ(2u, lists.getList0().size());
ASSERT_EQ(2u, lists.getList1().size()); ASSERT_EQ(4u, lists.getList1().size());
ASSERT_EQ(2u, lists.getList8().size()); ASSERT_EQ(2u, lists.getList8().size());
ASSERT_EQ(2u, lists.getList16().size()); ASSERT_EQ(2u, lists.getList16().size());
ASSERT_EQ(2u, lists.getList32().size()); ASSERT_EQ(2u, lists.getList32().size());
...@@ -391,6 +393,8 @@ void genericCheckListDefaults(Reader reader) { ...@@ -391,6 +393,8 @@ void genericCheckListDefaults(Reader reader) {
EXPECT_EQ(Void::VOID, lists.getList0()[1].getF()); EXPECT_EQ(Void::VOID, lists.getList0()[1].getF());
EXPECT_TRUE(lists.getList1()[0].getF()); EXPECT_TRUE(lists.getList1()[0].getF());
EXPECT_FALSE(lists.getList1()[1].getF()); EXPECT_FALSE(lists.getList1()[1].getF());
EXPECT_TRUE(lists.getList1()[2].getF());
EXPECT_TRUE(lists.getList1()[3].getF());
EXPECT_EQ(123u, lists.getList8()[0].getF()); EXPECT_EQ(123u, lists.getList8()[0].getF());
EXPECT_EQ(45u, lists.getList8()[1].getF()); EXPECT_EQ(45u, lists.getList8()[1].getF());
EXPECT_EQ(12345u, lists.getList16()[0].getF()); EXPECT_EQ(12345u, lists.getList16()[0].getF());
......
...@@ -308,10 +308,16 @@ struct TestLists { ...@@ -308,10 +308,16 @@ struct TestLists {
structListList @9 :List(List(TestAllTypes)); structListList @9 :List(List(TestAllTypes));
} }
struct TestFieldZeroIsBit {
bit @0 :Bool;
secondBit @1 :Bool = true;
thirdField @2 :UInt8 = 123;
}
struct TestListDefaults { struct TestListDefaults {
lists @0 :TestLists = ( lists @0 :TestLists = (
list0 = [(f = void), (f = void)], list0 = [(f = void), (f = void)],
list1 = [(f = true), (f = false)], list1 = [(f = true), (f = false), (f = true), (f = true)],
list8 = [(f = 123), (f = 45)], list8 = [(f = 123), (f = 45)],
list16 = [(f = 12345), (f = 6789)], list16 = [(f = 12345), (f = 6789)],
list32 = [(f = 123456789), (f = 234567890)], list32 = [(f = 123456789), (f = 234567890)],
......
...@@ -462,6 +462,19 @@ struct Id { ...@@ -462,6 +462,19 @@ struct Id {
// ======================================================================================= // =======================================================================================
// Units // Units
template <typename T> constexpr bool isIntegral() { return false; }
template <> constexpr bool isIntegral<char>() { return true; }
template <> constexpr bool isIntegral<signed char>() { return true; }
template <> constexpr bool isIntegral<short>() { return true; }
template <> constexpr bool isIntegral<int>() { return true; }
template <> constexpr bool isIntegral<long>() { return true; }
template <> constexpr bool isIntegral<long long>() { return true; }
template <> constexpr bool isIntegral<unsigned char>() { return true; }
template <> constexpr bool isIntegral<unsigned short>() { return true; }
template <> constexpr bool isIntegral<unsigned int>() { return true; }
template <> constexpr bool isIntegral<unsigned long>() { return true; }
template <> constexpr bool isIntegral<unsigned long long>() { return true; }
template <typename Number, typename Unit1, typename Unit2> template <typename Number, typename Unit1, typename Unit2>
class UnitRatio { class UnitRatio {
// A multiplier used to convert Quantities of one unit to Quantities of another unit. See // A multiplier used to convert Quantities of one unit to Quantities of another unit. See
...@@ -470,6 +483,8 @@ class UnitRatio { ...@@ -470,6 +483,8 @@ class UnitRatio {
// Construct this type by dividing one Quantity by another of a different unit. Use this type // Construct this type by dividing one Quantity by another of a different unit. Use this type
// by multiplying it by a Quantity, or dividing a Quantity by it. // by multiplying it by a Quantity, or dividing a Quantity by it.
static_assert(isIntegral<Number>(), "Underlying type for UnitRatio must be integer.");
public: public:
inline UnitRatio() {} inline UnitRatio() {}
...@@ -529,12 +544,14 @@ private: ...@@ -529,12 +544,14 @@ private:
friend class UnitRatio; friend class UnitRatio;
template <typename N1, typename N2, typename U1, typename U2> template <typename N1, typename N2, typename U1, typename U2>
friend inline constexpr decltype(N1(1) * N2(1)) operator*(N1, UnitRatio<N2, U1, U2>); friend inline constexpr UnitRatio<decltype(N1(1) * N2(1)), U1, U2>
operator*(N1, UnitRatio<N2, U1, U2>);
}; };
template <typename N1, typename N2, typename U1, typename U2> template <typename N1, typename N2, typename U1, typename U2>
inline constexpr decltype(N1(1) * N2(1)) operator*(N1 n, UnitRatio<N2, U1, U2> r) { inline constexpr UnitRatio<decltype(N1(1) * N2(1)), U1, U2>
return n * r.unit1PerUnit2; operator*(N1 n, UnitRatio<N2, U1, U2> r) {
return UnitRatio<decltype(N1(1) * N2(1)), U1, U2>(n * r.unit1PerUnit2);
} }
template <typename Number, typename Unit> template <typename Number, typename Unit>
...@@ -581,6 +598,8 @@ class Quantity { ...@@ -581,6 +598,8 @@ class Quantity {
// waitFor(3 * MINUTES); // waitFor(3 * MINUTES);
// } // }
static_assert(isIntegral<Number>(), "Underlying type for Quantity must be integer.");
public: public:
inline constexpr Quantity() {} inline constexpr Quantity() {}
...@@ -605,11 +624,13 @@ public: ...@@ -605,11 +624,13 @@ public:
template <typename OtherNumber> template <typename OtherNumber>
inline constexpr Quantity<decltype(Number(1) * OtherNumber(1)), Unit> inline constexpr Quantity<decltype(Number(1) * OtherNumber(1)), Unit>
operator*(OtherNumber other) const { operator*(OtherNumber other) const {
static_assert(isIntegral<OtherNumber>(), "Multiplied Quantity by non-integer.");
return Quantity<decltype(Number(1) * other), Unit>(value * other); return Quantity<decltype(Number(1) * other), Unit>(value * other);
} }
template <typename OtherNumber> template <typename OtherNumber>
inline constexpr Quantity<decltype(Number(1) / OtherNumber(1)), Unit> inline constexpr Quantity<decltype(Number(1) / OtherNumber(1)), Unit>
operator/(OtherNumber other) const { operator/(OtherNumber other) const {
static_assert(isIntegral<OtherNumber>(), "Divided Quantity by non-integer.");
return Quantity<decltype(Number(1) / other), Unit>(value / other); return Quantity<decltype(Number(1) / other), Unit>(value / other);
} }
template <typename OtherNumber> template <typename OtherNumber>
......
...@@ -708,8 +708,7 @@ packUnion :: UnionDesc -> PackingState -> Map.Map Integer UnionPackingState ...@@ -708,8 +708,7 @@ packUnion :: UnionDesc -> PackingState -> Map.Map Integer UnionPackingState
packUnion _ state unionState = (DataOffset Size16 offset, newState, unionState) where packUnion _ state unionState = (DataOffset Size16 offset, newState, unionState) where
(offset, newState) = packData Size16 state (offset, newState) = packData Size16 state
stripHolesFromFirstWord Size1 _ = error "can't get this far" stripHolesFromFirstWord Size1 _ = Size1 -- Stop at a bit.
stripHolesFromFirstWord Size8 _ = Size8 -- Don't reduce to less than a byte.
stripHolesFromFirstWord size holes = let stripHolesFromFirstWord size holes = let
nextSize = pred size nextSize = pred size
in case Map.lookup nextSize holes of in case Map.lookup nextSize holes of
...@@ -744,6 +743,7 @@ enforceFixed Nothing sizes = return sizes ...@@ -744,6 +743,7 @@ enforceFixed Nothing sizes = return sizes
enforceFixed (Just (Located pos (requestedDataSize, requestedPointerCount))) enforceFixed (Just (Located pos (requestedDataSize, requestedPointerCount)))
(actualDataSize, actualPointerCount) = do (actualDataSize, actualPointerCount) = do
validatedRequestedDataSize <- case requestedDataSize of validatedRequestedDataSize <- case requestedDataSize of
1 -> return DataSection1
8 -> return DataSection8 8 -> return DataSection8
16 -> return DataSection16 16 -> return DataSection16
32 -> return DataSection32 32 -> return DataSection32
......
...@@ -382,7 +382,7 @@ structContext parent desc = mkStrContext context where ...@@ -382,7 +382,7 @@ structContext parent desc = mkStrContext context where
context "structReferenceCount" = MuVariable $ structPointerCount desc context "structReferenceCount" = MuVariable $ structPointerCount desc
context "structPreferredListEncoding" = case (structDataSize desc, structPointerCount desc) of context "structPreferredListEncoding" = case (structDataSize desc, structPointerCount desc) of
(DataSectionWords 0, 0) -> MuVariable "VOID" (DataSectionWords 0, 0) -> MuVariable "VOID"
(DataSection1, 0) -> MuVariable "BYTE" (DataSection1, 0) -> MuVariable "BIT"
(DataSection8, 0) -> MuVariable "BYTE" (DataSection8, 0) -> MuVariable "BYTE"
(DataSection16, 0) -> MuVariable "TWO_BYTES" (DataSection16, 0) -> MuVariable "TWO_BYTES"
(DataSection32, 0) -> MuVariable "FOUR_BYTES" (DataSection32, 0) -> MuVariable "FOUR_BYTES"
......
...@@ -318,9 +318,9 @@ fieldSize (InlineStructType StructDesc { structDataSize = ds, structPointerCount ...@@ -318,9 +318,9 @@ fieldSize (InlineStructType StructDesc { structDataSize = ds, structPointerCount
fieldSize (InterfaceType _) = SizeReference fieldSize (InterfaceType _) = SizeReference
fieldSize (ListType _) = SizeReference fieldSize (ListType _) = SizeReference
fieldSize (InlineListType element size) = let fieldSize (InlineListType element size) = let
-- We intentionally do not allow single-bit lists because most CPUs cannot address bits.
minDataSectionForBits bits minDataSectionForBits bits
| bits <= 0 = DataSectionWords 0 | bits <= 0 = DataSectionWords 0
| bits <= 1 = DataSection1
| bits <= 8 = DataSection8 | bits <= 8 = DataSection8
| bits <= 16 = DataSection16 | bits <= 16 = DataSection16
| bits <= 32 = DataSection32 | bits <= 32 = DataSection32
......
...@@ -114,11 +114,12 @@ A list value is encoded as a pointer to a flat array of values. ...@@ -114,11 +114,12 @@ A list value is encoded as a pointer to a flat array of values.
The pointed-to values are tightly-packed. In particular, `Bool`s are packed bit-by-bit in The pointed-to values are tightly-packed. In particular, `Bool`s are packed bit-by-bit in
little-endian order (the first bit is the least-significant bit of the first byte). little-endian order (the first bit is the least-significant bit of the first byte).
Lists of structs use the smallest element size in which the struct can fit, except that single-bit Lists of structs use the smallest element size in which the struct can fit. So, a
structs are not allowed. So, a list of structs that each contain two `UInt8` fields and nothing list of structs that each contain two `UInt8` fields and nothing else could be encoded with C = 3
else could be encoded with C = 3 (2-byte elements). A list of structs that each contain a single (2-byte elements). A list of structs that each contain a single `Text` field would be encoded as
`Text` field would be encoded as C = 6 (pointer elements). A list structs which are each more than C = 6 (pointer elements). A list of structs that each contain a single `Bool` field would be
one word in size must be be encoded using C = 7 (composite). encoded using C = 1 (1-bit elements). A list structs which are each more than one word in size
must be be encoded using C = 7 (composite).
When C = 7, the elements of the list are fixed-width composite values -- usually, structs. In When C = 7, the elements of the list are fixed-width composite values -- usually, structs. In
this case, the list content is prefixed by a "tag" word that describes each individual element. this case, the list content is prefixed by a "tag" word that describes each individual element.
...@@ -136,7 +137,7 @@ elements being fixed-size lists rather than structs. In this case, the tag woul ...@@ -136,7 +137,7 @@ elements being fixed-size lists rather than structs. In this case, the tag woul
pointer rather than a struct pointer. As of this writing, no such feature has been implemented. pointer rather than a struct pointer. As of this writing, no such feature has been implemented.
Notice that because a small struct is encoded as if it were a primitive value, this means that Notice that because a small struct is encoded as if it were a primitive value, this means that
if you have a field of type `List(T)` where `T` is a primitive or blob type (other than `Bool`), it if you have a field of type `List(T)` where `T` is a primitive or blob type, it
is possible to change that field to `List(U)` where `U` is a struct whose `@0` field has type `T`, is possible to change that field to `List(U)` where `U` is a struct whose `@0` field has type `T`,
without breaking backwards-compatibility. This comes in handy when you discover too late that you without breaking backwards-compatibility. This comes in handy when you discover too late that you
need to associate some extra data with each value in a primitive list -- instead of using parallel need to associate some extra data with each value in a primitive list -- instead of using parallel
......
...@@ -489,12 +489,11 @@ A protocol can be changed in the following ways without breaking backwards-compa ...@@ -489,12 +489,11 @@ A protocol can be changed in the following ways without breaking backwards-compa
parameter list and must have default values. parameter list and must have default values.
* Any symbolic name can be changed, as long as the ordinal numbers stay the same. * Any symbolic name can be changed, as long as the ordinal numbers stay the same.
* Types definitions can be moved to different scopes. * Types definitions can be moved to different scopes.
* A field of type `List(T)`, where `T` is a primitive type (except `Bool`), non-inline blob, or * A field of type `List(T)`, where `T` is a primitive type, non-inline blob, or
non-inline list, may be changed to type `List(U)`, where `U` is a struct type whose `@0` field is non-inline list, may be changed to type `List(U)`, where `U` is a struct type whose `@0` field is
of type `T`. This rule is useful when you realize too late that you need to attach some extra of type `T`. This rule is useful when you realize too late that you need to attach some extra
data to each element of your list. Without this rule, you would be stuck defining parallel data to each element of your list. Without this rule, you would be stuck defining parallel
lists, which are ugly and error-prone. (`List(Bool)` does not support this transformation lists, which are ugly and error-prone.
because it would be difficult to implement given that booleans are packed 8 to the byte.)
* A struct that is not already `fixed` can be made `fixed`. However, once a struct is declared * A struct that is not already `fixed` can be made `fixed`. However, once a struct is declared
`fixed`, the declaration cannot be removed or changed, as this would change the layout of `Inline` `fixed`, the declaration cannot be removed or changed, as this would change the layout of `Inline`
uses of the struct. uses of the struct.
......
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