From ca277a4ef9b0c6e53b742ad2e49c3b646a04182d Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Tue, 25 Feb 2014 13:19:55 +0400 Subject: [PATCH] Tail array optimization; untested --- .../uavcan/internal/marshalling/array.hpp | 117 ++++++++++++------ .../internal/marshalling/float_spec.hpp | 4 +- .../internal/marshalling/integer_spec.hpp | 4 +- .../uavcan/internal/marshalling/type_util.hpp | 2 + libuavcan/test/marshalling/array.cpp | 75 +++++++---- libuavcan/test/marshalling/float_spec.cpp | 30 ++--- libuavcan/test/marshalling/integer_spec.cpp | 22 ++-- 7 files changed, 163 insertions(+), 91 deletions(-) diff --git a/libuavcan/include/uavcan/internal/marshalling/array.hpp b/libuavcan/include/uavcan/internal/marshalling/array.hpp index 017cb556e1..88e97eef9c 100644 --- a/libuavcan/include/uavcan/internal/marshalling/array.hpp +++ b/libuavcan/include/uavcan/internal/marshalling/array.hpp @@ -189,20 +189,88 @@ class Array : public ArrayImpl typedef ArrayImpl Base; typedef Array SelfType; - int encodeSize(ScalarCodec&, FalseType) const { return 1; } - int encodeSize(ScalarCodec& codec, TrueType) const // TODO: Tail array optimization + static bool isOptimizedTailArray(TailArrayOptimizationMode tao_mode) { - return Base::RawSizeType::encode(size(), codec); + return (T::MinBitLen >= 8) && (tao_mode == TailArrayOptEnabled); } - int decodeSize(ScalarCodec&, FalseType) { return 1; } - int decodeSize(ScalarCodec& codec, TrueType) + int encodeImpl(ScalarCodec& codec, const TailArrayOptimizationMode tao_mode, FalseType) const /// Static { - typename StorageType::Type sz = 0; - const int res = Base::RawSizeType::decode(sz, codec); - if (res > 0) + assert(size() > 0); + for (SizeType i = 0; i < size(); i++) + { + const bool last_item = i == (size() - 1); + const int res = RawValueType::encode(Base::at(i), codec, last_item ? tao_mode : TailArrayOptDisabled); + if (res <= 0) + return res; + } + return 1; + } + + int encodeImpl(ScalarCodec& codec, const TailArrayOptimizationMode tao_mode, TrueType) const /// Dynamic + { + StaticAssert::check(); + const bool self_tao_enabled = isOptimizedTailArray(tao_mode); + if (!self_tao_enabled) + { + const int res_sz = Base::RawSizeType::encode(size(), codec, TailArrayOptDisabled); + if (res_sz <= 0) + return res_sz; + } + if (size() == 0) + return 1; + return encodeImpl(codec, self_tao_enabled ? TailArrayOptDisabled : tao_mode, FalseType()); + } + + int decodeImpl(ScalarCodec& codec, const TailArrayOptimizationMode tao_mode, FalseType) /// Static + { + assert(size() > 0); + for (SizeType i = 0; i < size(); i++) + { + const bool last_item = i == (size() - 1); + ValueType value; // TODO: avoid extra copy + const int res = RawValueType::decode(value, codec, last_item ? tao_mode : TailArrayOptDisabled); + if (res <= 0) + return res; + Base::at(i) = value; + } + return 1; + } + + int decodeImpl(ScalarCodec& codec, const TailArrayOptimizationMode tao_mode, TrueType) /// Dynamic + { + StaticAssert::check(); + clear(); + if (isOptimizedTailArray(tao_mode)) + { + while (true) + { + ValueType value; + const int res = RawValueType::decode(value, codec, TailArrayOptDisabled); + if (res < 0) + return res; + if (res == 0) // Success: End of stream reached (even if zero items were read) + return 1; + if (size() == MaxSize_) // Error: Max array length reached, but the end of stream is not + return -1; + push_back(value); + } + } + else + { + typename StorageType::Type sz = 0; + const int res_sz = Base::RawSizeType::decode(sz, codec, TailArrayOptDisabled); + if (res_sz <= 0) + return res_sz; + if ((sz > 0) && ((sz - 1u) > (MaxSize_ - 1u))) // -Werror=type-limits + return -1; resize(sz); - return res; + if (sz == 0) + return 1; + return decodeImpl(codec, tao_mode, FalseType()); + } + assert(0); // Unreachable + return -1; } public: @@ -215,37 +283,16 @@ public: enum { IsDynamic = ArrayMode == ArrayModeDynamic }; enum { MaxSize = MaxSize_ }; enum { MinBitLen = IsDynamic ? 0 : (RawValueType::MinBitLen * MaxSize) }; - enum { MaxBitLen = (RawValueType::MaxBitLen * MaxSize) + (IsDynamic ? Base::SizeBitLen : 0) }; + enum { MaxBitLen = Base::SizeBitLen + RawValueType::MaxBitLen * MaxSize }; - static int encode(const SelfType& array, ScalarCodec& codec) + static int encode(const SelfType& array, ScalarCodec& codec, const TailArrayOptimizationMode tao_mode) { - const int res_sz = array.encodeSize(codec, BooleanType()); - if (res_sz <= 0) - return res_sz; - for (SizeType i = 0; i < array.size(); i++) - { - const int res = RawValueType::encode(array[i], codec); - if (res <= 0) - return res; - } - return 1; + return array.encodeImpl(codec, tao_mode, BooleanType()); } - static int decode(SelfType& array, ScalarCodec& codec) + static int decode(SelfType& array, ScalarCodec& codec, const TailArrayOptimizationMode tao_mode) { - const int res_sz = array.decodeSize(codec, BooleanType()); - if (res_sz <= 0) - return res_sz; - for (SizeType i = 0; i < array.size(); i++) - { - // TODO: avoid excessive copy - ValueType value; - const int res = RawValueType::decode(value, codec); - array[i] = value; - if (res <= 0) - return res; - } - return 1; + return array.decodeImpl(codec, tao_mode, BooleanType()); } bool empty() const { return size() == 0; } diff --git a/libuavcan/include/uavcan/internal/marshalling/float_spec.hpp b/libuavcan/include/uavcan/internal/marshalling/float_spec.hpp index eed9a5c365..3533244c3a 100644 --- a/libuavcan/include/uavcan/internal/marshalling/float_spec.hpp +++ b/libuavcan/include/uavcan/internal/marshalling/float_spec.hpp @@ -110,7 +110,7 @@ public: using IEEE754Limits::epsilon; static std::float_round_style roundstyle() { return IEEE754Converter::roundstyle(); } - static int encode(StorageType value, ScalarCodec& codec) + static int encode(StorageType value, ScalarCodec& codec, TailArrayOptimizationMode) { // cppcheck-suppress duplicateExpression if (CastMode == CastModeSaturate) @@ -120,7 +120,7 @@ public: return codec.encode(IEEE754Converter::toIeee(value)); } - static int decode(StorageType& out_value, ScalarCodec& codec) + static int decode(StorageType& out_value, ScalarCodec& codec, TailArrayOptimizationMode) { typename IntegerSpec::StorageType ieee = 0; const int res = codec.decode(ieee); diff --git a/libuavcan/include/uavcan/internal/marshalling/integer_spec.hpp b/libuavcan/include/uavcan/internal/marshalling/integer_spec.hpp index 32bc1b2283..505394db0d 100644 --- a/libuavcan/include/uavcan/internal/marshalling/integer_spec.hpp +++ b/libuavcan/include/uavcan/internal/marshalling/integer_spec.hpp @@ -79,7 +79,7 @@ public: // cppcheck-suppress duplicateExpression static StorageType min() { return Limits::min(); } - static int encode(StorageType value, ScalarCodec& codec) + static int encode(StorageType value, ScalarCodec& codec, TailArrayOptimizationMode) { // cppcheck-suppress duplicateExpression if (CastMode == CastModeSaturate) @@ -89,7 +89,7 @@ public: return codec.encode(value); } - static int decode(StorageType& out_value, ScalarCodec& codec) + static int decode(StorageType& out_value, ScalarCodec& codec, TailArrayOptimizationMode) { return codec.decode(out_value); } diff --git a/libuavcan/include/uavcan/internal/marshalling/type_util.hpp b/libuavcan/include/uavcan/internal/marshalling/type_util.hpp index 70211c32bc..93515b78bb 100644 --- a/libuavcan/include/uavcan/internal/marshalling/type_util.hpp +++ b/libuavcan/include/uavcan/internal/marshalling/type_util.hpp @@ -11,6 +11,8 @@ namespace uavcan enum CastMode { CastModeSaturate, CastModeTruncate }; +enum TailArrayOptimizationMode { TailArrayOptDisabled, TailArrayOptEnabled }; + template struct IntegerBitLen { enum { Result = 1 + IntegerBitLen<(Num >> 1)>::Result }; }; diff --git a/libuavcan/test/marshalling/array.cpp b/libuavcan/test/marshalling/array.cpp index bde222833b..1331bd9103 100644 --- a/libuavcan/test/marshalling/array.cpp +++ b/libuavcan/test/marshalling/array.cpp @@ -25,38 +25,40 @@ struct CustomType bool operator==(const CustomType& rhs) const { return a == rhs.a && b == rhs.b && c == rhs.c; } - static int encode(const CustomType& obj, uavcan::ScalarCodec& codec) + static int encode(const CustomType& obj, uavcan::ScalarCodec& codec, + uavcan::TailArrayOptimizationMode tao_mode = uavcan::TailArrayOptEnabled) { int res = 0; - res = A::encode(obj.a, codec); + res = A::encode(obj.a, codec, uavcan::TailArrayOptDisabled); if (res <= 0) return res; - res = B::encode(obj.b, codec); + res = B::encode(obj.b, codec, uavcan::TailArrayOptDisabled); if (res <= 0) return res; - res = C::encode(obj.c, codec); + res = C::encode(obj.c, codec, tao_mode); if (res <= 0) return res; return 1; } - static int decode(CustomType& obj, uavcan::ScalarCodec& codec) + static int decode(CustomType& obj, uavcan::ScalarCodec& codec, + uavcan::TailArrayOptimizationMode tao_mode = uavcan::TailArrayOptEnabled) { int res = 0; - res = A::decode(obj.a, codec); + res = A::decode(obj.a, codec, uavcan::TailArrayOptDisabled); if (res <= 0) return res; - res = B::decode(obj.b, codec); + res = B::decode(obj.b, codec, uavcan::TailArrayOptDisabled); if (res <= 0) return res; - res = C::decode(obj.c, codec); + res = C::decode(obj.c, codec, tao_mode); if (res <= 0) return res; @@ -143,16 +145,17 @@ TEST(Array, Basic) /* * Representation check + * Note that TAO in A3 is not possible because A3::C has less than one byte per item */ uavcan::StaticTransferBuffer<16> buf; uavcan::BitStream bs_wr(buf); uavcan::ScalarCodec sc_wr(bs_wr); - ASSERT_EQ(1, A1::encode(a1, sc_wr)); - ASSERT_EQ(1, A2::encode(a2, sc_wr)); - ASSERT_EQ(1, A3::encode(a3, sc_wr)); + ASSERT_EQ(1, A1::encode(a1, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, A2::encode(a2, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, A3::encode(a3, sc_wr, uavcan::TailArrayOptEnabled)); - ASSERT_EQ(0, A3::encode(a3, sc_wr)); // Out of buffer space + ASSERT_EQ(0, A3::encode(a3, sc_wr, uavcan::TailArrayOptEnabled)); // Out of buffer space static const std::string Reference = "00000000 00000001 00000010 00000011 " // A1 (0, 1, 2, 3) @@ -172,9 +175,9 @@ TEST(Array, Basic) A2 a2_; A3 a3_; - ASSERT_EQ(1, A1::decode(a1_, sc_rd)); - ASSERT_EQ(1, A2::decode(a2_, sc_rd)); - ASSERT_EQ(1, A3::decode(a3_, sc_rd)); + ASSERT_EQ(1, A1::decode(a1_, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, A2::decode(a2_, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, A3::decode(a3_, sc_rd, uavcan::TailArrayOptEnabled)); ASSERT_EQ(a1_, a1); ASSERT_EQ(a2_, a2); @@ -193,7 +196,7 @@ TEST(Array, Basic) ASSERT_EQ(a3[i].c, a3_[i].c); } - ASSERT_EQ(0, A3::decode(a3_, sc_rd)); // Out of buffer space + ASSERT_EQ(0, A3::decode(a3_, sc_rd, uavcan::TailArrayOptEnabled)); // Out of buffer space /* * STL compatibility @@ -252,31 +255,36 @@ TEST(Array, Dynamic) A a; B b; + B b2; ASSERT_EQ(3 + 5, A::MaxBitLen); ASSERT_EQ(8 + 255 * 8, B::MaxBitLen); ASSERT_TRUE(a.empty()); ASSERT_TRUE(b.empty()); + ASSERT_TRUE(b2.empty()); { uavcan::StaticTransferBuffer<16> buf; uavcan::BitStream bs_wr(buf); uavcan::ScalarCodec sc_wr(bs_wr); - ASSERT_EQ(1, A::encode(a, sc_wr)); - ASSERT_EQ(1, B::encode(b, sc_wr)); + ASSERT_EQ(1, A::encode(a, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::encode(b, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::encode(b2, sc_wr, uavcan::TailArrayOptEnabled)); - ASSERT_EQ("000" "00000 000" "00000", bs_wr.toString()); + ASSERT_EQ("000" "00000 000" "00000", bs_wr.toString()); // Last array was optimized away completely uavcan::BitStream bs_rd(buf); uavcan::ScalarCodec sc_rd(bs_rd); - ASSERT_EQ(1, A::decode(a, sc_rd)); - ASSERT_EQ(1, B::decode(b, sc_rd)); + ASSERT_EQ(1, A::decode(a, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::decode(b, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::decode(b2, sc_rd, uavcan::TailArrayOptEnabled)); ASSERT_TRUE(a.empty()); ASSERT_TRUE(b.empty()); + ASSERT_TRUE(b2.empty()); } a.push_back(true); @@ -288,26 +296,38 @@ TEST(Array, Dynamic) b.push_back(42); b.push_back(-42); + b2.push_back(123); + b2.push_back(72); + { uavcan::StaticTransferBuffer<16> buf; uavcan::BitStream bs_wr(buf); uavcan::ScalarCodec sc_wr(bs_wr); - ASSERT_EQ(1, A::encode(a, sc_wr)); - ASSERT_EQ(1, B::encode(b, sc_wr)); + ASSERT_EQ(1, A::encode(a, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::encode(b, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::encode(b2, sc_wr, uavcan::TailArrayOptEnabled)); // No length field - ASSERT_EQ("10110101 00000010 00101010 11010110", bs_wr.toString()); + // A B len B[0] B[1] B2[0] B2[1] + ASSERT_EQ("10110101 00000010 00101010 11010110 01111011 01001000", bs_wr.toString()); uavcan::BitStream bs_rd(buf); uavcan::ScalarCodec sc_rd(bs_rd); a.clear(); b.clear(); + b2.clear(); ASSERT_TRUE(a.empty()); ASSERT_TRUE(b.empty()); + ASSERT_TRUE(b2.empty()); - ASSERT_EQ(1, A::decode(a, sc_rd)); - ASSERT_EQ(1, B::decode(b, sc_rd)); + ASSERT_EQ(1, A::decode(a, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::decode(b, sc_rd, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, B::decode(b2, sc_rd, uavcan::TailArrayOptEnabled)); + + ASSERT_EQ(5, a.size()); + ASSERT_EQ(2, b.size()); + ASSERT_EQ(2, b2.size()); ASSERT_TRUE(a[0]); ASSERT_FALSE(a[1]); @@ -317,6 +337,9 @@ TEST(Array, Dynamic) ASSERT_EQ(42, b[0]); ASSERT_EQ(-42, b[1]); + + ASSERT_EQ(123, b2[0]); + ASSERT_EQ(72, b2[1]); } ASSERT_FALSE(a == b); diff --git a/libuavcan/test/marshalling/float_spec.cpp b/libuavcan/test/marshalling/float_spec.cpp index 48d4c8d3d3..bbabd6a387 100644 --- a/libuavcan/test/marshalling/float_spec.cpp +++ b/libuavcan/test/marshalling/float_spec.cpp @@ -103,15 +103,15 @@ TEST(FloatSpec, Basic) for (int i = 0; i < NumValues; i++) { - ASSERT_EQ(1, F16S::encode(Values[i], sc_wr)); - ASSERT_EQ(1, F16T::encode(Values[i], sc_wr)); - ASSERT_EQ(1, F32S::encode(Values[i], sc_wr)); - ASSERT_EQ(1, F32T::encode(Values[i], sc_wr)); - ASSERT_EQ(1, F64S::encode(Values[i], sc_wr)); - ASSERT_EQ(1, F64T::encode(Values[i], sc_wr)); + ASSERT_EQ(1, F16S::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F16T::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F32S::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F32T::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F64S::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F64T::encode(Values[i], sc_wr, uavcan::TailArrayOptDisabled)); } - ASSERT_EQ(0, F16S::encode(0, sc_wr)); // Out of buffer space now + ASSERT_EQ(0, F16S::encode(0, sc_wr, uavcan::TailArrayOptDisabled)); // Out of buffer space now /* * Reading @@ -122,7 +122,7 @@ TEST(FloatSpec, Basic) #define CHECK(FloatType, expected_value) \ do { \ StorageType::Type var = StorageType::Type(); \ - ASSERT_EQ(1, FloatType::decode(var, sc_rd)); \ + ASSERT_EQ(1, FloatType::decode(var, sc_rd, uavcan::TailArrayOptDisabled)); \ if (!isnan(expected_value)) \ ASSERT_FLOAT_EQ(expected_value, var); \ else \ @@ -155,14 +155,14 @@ TEST(FloatSpec, Float16Representation) uavcan::BitStream bs_wr(buf); uavcan::ScalarCodec sc_wr(bs_wr); - ASSERT_EQ(1, F16S::encode(0.0, sc_wr)); - ASSERT_EQ(1, F16S::encode(1.0, sc_wr)); - ASSERT_EQ(1, F16S::encode(-2.0, sc_wr)); - ASSERT_EQ(1, F16T::encode(999999, sc_wr)); // +inf - ASSERT_EQ(1, F16S::encode(-999999, sc_wr)); // -max - ASSERT_EQ(1, F16S::encode(nan(""), sc_wr)); // nan + ASSERT_EQ(1, F16S::encode(0.0, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F16S::encode(1.0, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F16S::encode(-2.0, sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, F16T::encode(999999, sc_wr, uavcan::TailArrayOptDisabled)); // +inf + ASSERT_EQ(1, F16S::encode(-999999, sc_wr, uavcan::TailArrayOptDisabled)); // -max + ASSERT_EQ(1, F16S::encode(nan(""), sc_wr, uavcan::TailArrayOptDisabled)); // nan - ASSERT_EQ(0, F16S::encode(0, sc_wr)); // Out of buffer space now + ASSERT_EQ(0, F16S::encode(0, sc_wr, uavcan::TailArrayOptDisabled)); // Out of buffer space now static const std::string Reference = // Keep in mind that this is LITTLE ENDIAN representation "00000000 00000000 " // 0.0 diff --git a/libuavcan/test/marshalling/integer_spec.cpp b/libuavcan/test/marshalling/integer_spec.cpp index 732d7dfee1..5bac591682 100644 --- a/libuavcan/test/marshalling/integer_spec.cpp +++ b/libuavcan/test/marshalling/integer_spec.cpp @@ -70,15 +70,15 @@ TEST(IntegerSpec, Basic) typedef IntegerSpec<10, SignednessSigned, CastModeSaturate> SInt10S; typedef IntegerSpec<1, SignednessUnsigned, CastModeSaturate> UInt1S; - ASSERT_EQ(1, UInt8S::encode(UInt8S::StorageType(123), sc_wr)); - ASSERT_EQ(1, SInt4T::encode(SInt4T::StorageType(-0x44), sc_wr)); - ASSERT_EQ(1, UInt32T::encode(UInt32T::StorageType(0xFFFFFFFF), sc_wr)); - ASSERT_EQ(1, UInt40S::encode(UInt40S::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr)); - ASSERT_EQ(1, UInt64T::encode(UInt64T::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr)); - ASSERT_EQ(1, SInt58S::encode(SInt58S::StorageType(0xFFFFFFFFFFFFFFF), sc_wr)); - ASSERT_EQ(1, UInt63S::encode(UInt63S::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr)); - ASSERT_EQ(1, SInt10S::encode(SInt10S::StorageType(-30000), sc_wr)); - ASSERT_EQ(1, UInt1S::encode(UInt1S::StorageType(42), sc_wr)); + ASSERT_EQ(1, UInt8S::encode(UInt8S::StorageType(123), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, SInt4T::encode(SInt4T::StorageType(-0x44), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, UInt32T::encode(UInt32T::StorageType(0xFFFFFFFF), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, UInt40S::encode(UInt40S::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, UInt64T::encode(UInt64T::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, SInt58S::encode(SInt58S::StorageType(0xFFFFFFFFFFFFFFF), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, UInt63S::encode(UInt63S::StorageType(0xFFFFFFFFFFFFFFFF), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, SInt10S::encode(SInt10S::StorageType(-30000), sc_wr, uavcan::TailArrayOptDisabled)); + ASSERT_EQ(1, UInt1S::encode(UInt1S::StorageType(42), sc_wr, uavcan::TailArrayOptDisabled)); std::cout << bs_wr.toString() << std::endl; @@ -88,7 +88,7 @@ TEST(IntegerSpec, Basic) #define CHECK(IntType, expected_value) \ do { \ StorageType::Type var = StorageType::Type(); \ - ASSERT_EQ(1, IntType::decode(var, sc_rd)); \ + ASSERT_EQ(1, IntType::decode(var, sc_rd, uavcan::TailArrayOptDisabled)); \ ASSERT_EQ(expected_value, var); \ } while (0) @@ -105,5 +105,5 @@ TEST(IntegerSpec, Basic) #undef CHECK StorageType::Type var; - ASSERT_EQ(0, UInt1S::decode(var, sc_rd)); + ASSERT_EQ(0, UInt1S::decode(var, sc_rd, uavcan::TailArrayOptDisabled)); }