From 85532557750343d914d35bd732c95b2756f706ed Mon Sep 17 00:00:00 2001 From: Scott Date: Sat, 3 Aug 2019 22:53:27 -0700 Subject: [PATCH] Progress on #235, adding unaligned copy. Adding a version of unaligned copy for little-endian platforms that provides a single abstraction for vectorized unaligned copy. This replaces the v0 BitStream + unalignedCopy abstractions making a v1 "bit stream" into simply an std::vector and a std::size_t bit_offset. The abstraction allows the vector's word size to be parameterized which could provide better optimizations where a given architecture can perform single operations across multiple bytes instead of hard-coding all 8-bit operations. The usefulness versus complexity of this optimization will play out in subsequent pull-requests as we start utilizing this copy operation in the generated dsdl types. Progress on #235, adding unaligned copy Version 2 of this utility. This one is endian agnostic (I think). --- .vscode/settings.json | 27 +- .../include/libuavcan/platform/memory.hpp | 94 +++++ .../include/lvs/lvs.hpp | 1 + .../include/lvs/platform/memory_copy.hpp | 378 ++++++++++++++++++ test/compile/test_media_interfaces_002.cpp | 2 +- .../native/test_platform_memory_copy_bits.cpp | 8 + 6 files changed, 508 insertions(+), 2 deletions(-) create mode 100644 libuavcan_validation_suite/include/lvs/platform/memory_copy.hpp create mode 100644 test/native/test_platform_memory_copy_bits.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 7237228cb..7a0953e42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,7 +52,32 @@ "functional": "cpp", "iomanip": "cpp", "ratio": "cpp", - "thread": "cpp" + "thread": "cpp", + "__bit_reference": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__functional_base": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "atomic": "cpp", + "bitset": "cpp", + "cstdarg": "cpp", + "ios": "cpp", + "iterator": "cpp", + "locale": "cpp", + "map": "cpp", + "mutex": "cpp", + "set": "cpp", + "string": "cpp" }, "C_Cpp.default.cppStandard": "c++11", "C_Cpp.default.cStandard": "c99", diff --git a/libuavcan/include/libuavcan/platform/memory.hpp b/libuavcan/include/libuavcan/platform/memory.hpp index 90bdda7ff..e480cfb3f 100644 --- a/libuavcan/include/libuavcan/platform/memory.hpp +++ b/libuavcan/include/libuavcan/platform/memory.hpp @@ -304,6 +304,100 @@ class LIBUAVCAN_EXPORT PoolAllocator } }; +/** + * Copy bits from a byte array using arbitrary alignment to an aligned byte array. + * + * @param src The byte array to copy from. + * @param src_offset_bits The offset, in bits, from the start of the src array to + * start copying from. + * @param dst The byte array to copy data into. + * @param length_bits The total length of bits to copy. The caller must ensure + * that the size of src and dst are >= this value. + * + * @return The number of bits copied. + */ +inline std::size_t copyBitsUnalignedToAligned(const std::uint8_t* const src, + const std::size_t src_offset_bits, + std::uint8_t* const dst, + const std::size_t length_bits) +{ + if (nullptr == src || nullptr == dst || length_bits == 0) + { + return 0; + } + std::size_t bits_copied = 0; + std::size_t offset_bits = src_offset_bits; + const std::size_t local_offset = src_offset_bits % 8U; + do + { + std::size_t current_byte = offset_bits / 8U; + const std::size_t bits_from_src_byte = 8U - local_offset; + bits_copied += std::min(length_bits, bits_from_src_byte); + dst[current_byte] &= static_cast(0xFF << bits_from_src_byte); + dst[current_byte] |= static_cast(src[current_byte] >> local_offset); + offset_bits += 8U; + if (offset_bits < length_bits) + { + current_byte = offset_bits / 8U; + dst[current_byte] = static_cast(src[current_byte] << bits_from_src_byte); + bits_copied += local_offset; + } + else + { + // we don't need to reevaluate the while condition. + break; + } + } while (true); + return bits_copied; +} + +/** + * Copy aligned bits from a byte array to another byte array using arbitrary alignment. + * + * @param src The byte array to copy from. + * @param dst The byte array to copy data into. + * @param dst_offset_bits The offset, in bits, from the start of the dst array to + * start writing to. + * @param length_bits The total length of bits to copy. The caller must ensure + * that the size of src and dst are >= this value. + * + * @return The number of bits copied. + */ +inline std::size_t copyBitsAlignedToUnaligned(const std::uint8_t* const src, + std::uint8_t* const dst, + const std::size_t dst_offset_bits, + const std::size_t length_bits) +{ + if (nullptr == src || nullptr == dst || length_bits == 0) + { + return 0; + } + std::size_t bits_copied = 0; + std::size_t offset_bits = dst_offset_bits; + const std::size_t local_offset = dst_offset_bits % 8U; + do + { + std::size_t current_byte = offset_bits / 8U; + const std::size_t bits_from_src_byte = 8U - local_offset; + dst[current_byte] &= static_cast(0xFF >> bits_from_src_byte); + dst[current_byte] |= static_cast(src[current_byte] << local_offset); + offset_bits += 8U; + bits_copied += std::min(length_bits, bits_from_src_byte); + if (offset_bits < length_bits) + { + dst[current_byte] |= static_cast(src[offset_bits / 8U] >> bits_from_src_byte); + bits_copied += local_offset; + } + else + { + // we don't need to reevaluate the while condition. + break; + } + } while (true); + + return bits_copied; +} + } // namespace memory } // namespace platform } // namespace libuavcan diff --git a/libuavcan_validation_suite/include/lvs/lvs.hpp b/libuavcan_validation_suite/include/lvs/lvs.hpp index 2cc361a14..eaa1749da 100644 --- a/libuavcan_validation_suite/include/lvs/lvs.hpp +++ b/libuavcan_validation_suite/include/lvs/lvs.hpp @@ -8,6 +8,7 @@ #define LIBUAVCAN_LVS_HPP_INCLUDED #include "gtest/gtest.h" +#include "gmock/gmock.h" #include "libuavcan/libuavcan.hpp" #include diff --git a/libuavcan_validation_suite/include/lvs/platform/memory_copy.hpp b/libuavcan_validation_suite/include/lvs/platform/memory_copy.hpp new file mode 100644 index 000000000..5c5982804 --- /dev/null +++ b/libuavcan_validation_suite/include/lvs/platform/memory_copy.hpp @@ -0,0 +1,378 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + */ +/** @file + * Include this test in a google test application to verify unaligned bit copy + * for your platform. + */ +#ifndef LIBUAVCAN_LVS_PLATFORM_MEMORY_COPY_HPP_INCLUDED +#define LIBUAVCAN_LVS_PLATFORM_MEMORY_COPY_HPP_INCLUDED + +#include "lvs/lvs.hpp" +#include "libuavcan/platform/memory.hpp" + +namespace lvs +{ +namespace platform +{ +namespace memory +{ +/** + * Cover all mundane cases where inputs are out of range or invalid. + */ +TEST(CopyBitsTest, InputsValidation) +{ + std::uint8_t* dummy_null = nullptr; + // Just allocate a bunch of memory on the stack to keep the tests from segfaulting. + // For the input validation test we don't care about the contents of this memory. + std::uint8_t dummy[] = {0, 0, 0, 0}; + constexpr std::size_t dummy_length_bits = std::extent::value * 8; + static_assert(dummy_length_bits > 16, "Test expects more than 2 bytes in the dummy dataset."); + + // null pointers + ASSERT_EQ(0, + libuavcan::platform::memory::copyBitsUnalignedToAligned(dummy_null, + 0, + dummy, + dummy_length_bits)); + ASSERT_EQ(0, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(dummy_null, + dummy, + 0, + dummy_length_bits)); + ASSERT_EQ(0, + libuavcan::platform::memory::copyBitsUnalignedToAligned(dummy, + 0, + dummy_null, + dummy_length_bits)); + ASSERT_EQ(0, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(dummy, + dummy_null, + 0, + dummy_length_bits)); + + // zero length arrays. + ASSERT_EQ(0, libuavcan::platform::memory::copyBitsUnalignedToAligned(dummy, 0, dummy, 0)); + ASSERT_EQ(0, libuavcan::platform::memory::copyBitsAlignedToUnaligned(dummy, dummy, 0, 0)); +} + +// +--------------------------------------------------------------------------+ +// | TEST CASES :: ONE BYTE +// +--------------------------------------------------------------------------+ +/** + * Copy from one byte aligned into another aligned. + */ +TEST(CopyBitsTest, OneByteAlignedIntoOneAligned) +{ + const std::uint8_t src[] = {0x55}; + std::uint8_t dst[] = {0xFF}; + constexpr std::size_t bits_to_copy = sizeof(std::uint8_t) * 8; + ASSERT_EQ(bits_to_copy, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, 0, dst, bits_to_copy)); + ASSERT_EQ(src[0], dst[0]); + ASSERT_EQ(bits_to_copy, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, dst, 0, bits_to_copy)); + ASSERT_EQ(src[0], dst[0]); +} + +/** + * Copy from one byte aligned into another unaligned. + * + * @code + * dst (iit) = 11111111 + * src = 01010101 + * dst (result) = 10101011 + * + * dst (iit) = 00000000 + * src = 01010101 + * dst (result) = 10101010 + * @endcode + */ +TEST(CopyBitsTest, OneByteAlignedIntoOneUnaligned) +{ + const std::uint8_t src[] = {0x55}; + std::uint8_t dst[] = {0xFF}; + constexpr std::size_t bits_to_copy = sizeof(std::uint8_t) * 8; + constexpr std::size_t dst_offset = 1; + std::size_t expected_bits_written = bits_to_copy - dst_offset; + ASSERT_EQ(expected_bits_written, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, + dst, + dst_offset, + bits_to_copy)); + ASSERT_EQ(0xAB, dst[0]); + + dst[0] = 0x00; + ASSERT_EQ(expected_bits_written, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, + dst, + dst_offset, + bits_to_copy)); + ASSERT_EQ(0xAA, dst[0]); +} + +/** + * Copy from one byte unaligned into another aligned. + * + * @code + * dst (init) = 11111111 + * src = 01010101 + * dst (result) = 10101010 + * + * dst (init) = 00000000 + * src = 01010101 + * dst (result) = 10101010 + * @endcode + */ +TEST(CopyBitsTest, OneByteUnalignedIntoOneAligned) +{ + const std::uint8_t src[] = {0x55}; + std::uint8_t dst[] = {0xFF}; + constexpr std::size_t src_offset = 1; + constexpr std::size_t bits_to_copy = sizeof(std::uint8_t) * 8; + ASSERT_EQ(bits_to_copy - src_offset, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, + src_offset, + dst, + bits_to_copy - src_offset)); + ASSERT_EQ(0xAA, dst[0]); + dst[0] = 0x00; + ASSERT_EQ(bits_to_copy - src_offset, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, + src_offset, + dst, + bits_to_copy - src_offset)); + ASSERT_EQ(0x2A, dst[0]); +} + +/** + * Copy just two bits from an unaligned source. + * + * @code + * dst (init) = 10000000 + * src = 11111111 + * dst (result) = 10000011 + * + * @endcode + */ +TEST(CopyBitsTest, TwoBitsUnalignedIntoAligned) +{ + const std::uint8_t src[] = {0xFF}; + std::uint8_t dst[] = {0x80}; + constexpr std::size_t src_offset = 6; + ASSERT_EQ(2, libuavcan::platform::memory::copyBitsUnalignedToAligned(src, src_offset, dst, 2)); + ASSERT_EQ(0x83, dst[0]); +} + +/** + * Copy just two bits to an unaligned destination. + * + * @code + * dst (init) = 00000001 + * src = 11111111 + * dst (result) = 11000001 + * + * @endcode + */ +TEST(CopyBitsTest, TwoBitsAlignedIntoUnaligned) +{ + const std::uint8_t src[] = {0xFF}; + std::uint8_t dst[] = {0x1}; + constexpr std::size_t dst_offset = 6; + ASSERT_EQ(2, libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, dst, dst_offset, 2)); + ASSERT_EQ(0xC1, dst[0]); +} + + +// +--------------------------------------------------------------------------+ +// | TEST CASES :: MULTI BYTE +// +--------------------------------------------------------------------------+ + +/** + * Copy from two integers aligned into two aligned. + */ +TEST(CopyBitsTest, TwoByteAlignedIntoTwoAligned) +{ + std::uint8_t dst0[] = {0xFF, 0xFF}; + std::uint8_t dst1[] = {0xFF, 0xFF}; + std::uint8_t src[] = {0x55, 0x55}; + std::uint8_t expected[] = {0x55, 0x55}; + + const std::size_t length_bits = sizeof(std::uint8_t) * 16; + ASSERT_EQ(length_bits, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, 0, dst0, length_bits)); + ASSERT_THAT(dst0, ::testing::ElementsAreArray(expected)); + ASSERT_EQ(length_bits, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, dst1, 0, length_bits)); + ASSERT_THAT(dst1, ::testing::ElementsAreArray(expected)); +} + +/** + * Copy from two integers unaligned into two aligned. + * @code + * dst (init) = 11111111 11111111 + * src = 01010101 01010101 + * dst (result) = 10101010 10101010 + * + * dst (init) = 00000000 00000000 + * src = 01010101 01010101 + * dst (result) = 00101010 10101010 + * @endcode + */ +TEST(CopyBitsTest, TwoByteUnalignedIntoTwoAligned) +{ + std::uint8_t dst[] = {0xFF, 0xFF}; + std::uint8_t src[] = {0x55, 0x55}; + std::uint8_t expected[] = {0xAA, 0xAA}; + + const std::size_t length_bits = sizeof(std::uint8_t) * 16; + ASSERT_EQ(length_bits - 1, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, 1, dst, length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); + + dst[0] = 0; + dst[1] = 0; + expected[0] = 0x2A; + expected[1] = 0xAA; + ASSERT_EQ(length_bits - 1, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, 1, dst, length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); +} + +/** + * Copy from two integers aligned into two unaligned. + * dst (init) = 11111111 11111111 + * src = 10101010 10101010 + * dst (result) = 01010101 01010101 + * + * dst (init) = 00000000 00000000 + * src = 10101010 10101010 + * dst (result) = 01010101 01010100 + */ +TEST(CopyBitsTest, TwoByteAlignedIntoTwoUnaligned) +{ + std::uint8_t dst[] = {0xFF, 0xFF}; + std::uint8_t src[] = {0xAA, 0xAA}; + std::uint8_t expected[] = {0x55, 0x55}; + + const std::size_t length_bits = sizeof(std::uint8_t) * 16; + ASSERT_EQ(length_bits - 1, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, dst, 1, length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); + + dst[0] = 0; + dst[1] = 0; + expected[0] = 0x55; + expected[1] = 0x54; + + ASSERT_EQ(length_bits - 1, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, dst, 1, length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); +} + +/** + * Sanity check that I'm not confused. Bits can be hard to reason about + * but words either make sense or don't. + */ +TEST(CopyBitsTest, StringUnalignedIntoStringAligned) +{ + std::uint8_t dst[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + std::uint8_t src[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const std::uint8_t expected[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x00}; + constexpr std::size_t src_length = std::extent::value; + static_assert(src_length == std::extent::value, + "expected string is shorter then input to copy."); + + for (std::size_t i = 0; i < src_length; ++i) + { + src[i] = static_cast(expected[i] << 1); + } + const std::size_t length_bits = src_length * 8; + ASSERT_EQ(length_bits - 1, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, 1, dst, length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); +} + +/** + * Unaligned to aligned where the offset is greater than one byte. + * dst (init) = 11111111 11111111 + * src = 01010101 01010101 + * dst (result) = 11111111 10101010 + * + * dst (init) = 00000000 00000000 + * src = 01010101 01010101 + * dst (result) = 00000000 00101010 + * + */ +TEST(CopyBitsTest, SrcOffsetIsGreaterThanEight) +{ + std::uint8_t dst[] = {0xFF, 0xFF}; + std::uint8_t src[] = {0x55, 0x55}; + std::uint8_t expected[] = {0xFF, 0xAA}; + + constexpr std::size_t src_length = std::extent::value; + const std::size_t length_bits = src_length * 8; + const std::size_t src_offset_bits = 9U; + ASSERT_EQ(length_bits - src_offset_bits, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, + src_offset_bits, + dst, + length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); + + dst[0] = 0; + dst[1] = 0; + expected[0] = 0; + expected[1] = 0x2A; + ASSERT_EQ(length_bits - src_offset_bits, + libuavcan::platform::memory::copyBitsUnalignedToAligned(src, + src_offset_bits, + dst, + length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); +} + +/** + * Aligned to unaligned where the offset is greater than one byte. + * dst (init) = 11111111 11111111 + * src = 01010101 01010101 + * dst (result) = 11111111 10101011 + * + * dst (init) = 00000000 00000000 + * src = 01010101 01010101 + * dst (result) = 00000000 10101010 + * + */ +TEST(CopyBitsTest, DstOffsetIsGreaterThanEight) +{ + std::uint8_t dst[] = {0xFF, 0xFF}; + std::uint8_t src[] = {0x55, 0x55}; + std::uint8_t expected[] = {0xFF, 0xAB}; + + constexpr std::size_t src_length = std::extent::value; + const std::size_t length_bits = src_length * 8; + const std::size_t dst_offset_bits = 9U; + ASSERT_EQ(length_bits - dst_offset_bits, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, + dst, + dst_offset_bits, + length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); + + dst[0] = 0; + dst[1] = 0; + expected[0] = 0; + expected[1] = 0xAA; + ASSERT_EQ(length_bits - dst_offset_bits, + libuavcan::platform::memory::copyBitsAlignedToUnaligned(src, + dst, + dst_offset_bits, + length_bits)); + ASSERT_THAT(dst, ::testing::ElementsAreArray(expected)); +} + +} // namespace memory +} // namespace platform +} // end namespace lvs + +#endif // LIBUAVCAN_LVS_PLATFORM_MEMORY_COPY_HPP_INCLUDED diff --git a/test/compile/test_media_interfaces_002.cpp b/test/compile/test_media_interfaces_002.cpp index 159d8f348..b80b1dcd0 100644 --- a/test/compile/test_media_interfaces_002.cpp +++ b/test/compile/test_media_interfaces_002.cpp @@ -6,7 +6,7 @@ #include "libuavcan/media/can.hpp" class Dummy : public libuavcan::media:: - Interface, 0, 4> + InterfaceGroup, 0, 4> { public: virtual std::uint_fast8_t getInterfaceCount() const override diff --git a/test/native/test_platform_memory_copy_bits.cpp b/test/native/test_platform_memory_copy_bits.cpp new file mode 100644 index 000000000..e63f8febe --- /dev/null +++ b/test/native/test_platform_memory_copy_bits.cpp @@ -0,0 +1,8 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Unit tests of the unaligned copy facilities in the libuavcan platform layer. + */ +#include "lvs/lvs.hpp" + +#include "lvs/platform/memory_copy.hpp"