|  | // Copyright lowRISC contributors. | 
|  | // Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  |  | 
|  | #include "sw/device/lib/base/memory.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <stdint.h> | 
|  | #include <vector> | 
|  |  | 
|  | #include "gmock/gmock.h" | 
|  | #include "gtest/gtest-message.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | namespace memory_unittest { | 
|  | namespace { | 
|  |  | 
|  | // Wrappers for builtins because they must be called directly when building with | 
|  | // clang. | 
|  |  | 
|  | void *builtin_memcpy_wrapper(void *dest, const void *src, size_t count) { | 
|  | return __builtin_memcpy(dest, src, count); | 
|  | } | 
|  |  | 
|  | int builtin_memcmp_wrapper(const void *lhs, const void *rhs, size_t count) { | 
|  | return __builtin_memcmp(lhs, rhs, count); | 
|  | } | 
|  |  | 
|  | void *builtin_memset_wrapper(void *dest, int ch, size_t count) { | 
|  | return __builtin_memset(dest, ch, count); | 
|  | } | 
|  |  | 
|  | // Reference implementations of memory functions. | 
|  |  | 
|  | enum { | 
|  | kMemCmpEq = 0, | 
|  | kMemCmpLt = -42, | 
|  | kMemCmpGt = 42, | 
|  | }; | 
|  |  | 
|  | int ref_memrcmp(const void *lhs, const void *rhs, size_t len) { | 
|  | const uint8_t *lhs8 = (uint8_t *)lhs; | 
|  | const uint8_t *rhs8 = (uint8_t *)rhs; | 
|  | size_t j; | 
|  | for (size_t i = 0; i < len; ++i) { | 
|  | j = len - 1 - i; | 
|  | if (lhs8[j] < rhs8[j]) { | 
|  | return kMemCmpLt; | 
|  | } else if (lhs8[j] > rhs8[j]) { | 
|  | return kMemCmpGt; | 
|  | } | 
|  | } | 
|  | return kMemCmpEq; | 
|  | } | 
|  |  | 
|  | void *ref_memchr(const void *ptr, int value, size_t len) { | 
|  | uint8_t *ptr8 = (uint8_t *)ptr; | 
|  | uint8_t value8 = (uint8_t)value; | 
|  | for (size_t i = 0; i < len; ++i) { | 
|  | if (ptr8[i] == value8) { | 
|  | return ptr8 + i; | 
|  | } | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | void *ref_memrchr(const void *ptr, int value, size_t len) { | 
|  | uint8_t *ptr8 = (uint8_t *)ptr; | 
|  | uint8_t value8 = (uint8_t)value; | 
|  | for (size_t i = 0; i < len; ++i) { | 
|  | size_t idx = len - i - 1; | 
|  | if (ptr8[idx] == value8) { | 
|  | return ptr8 + idx; | 
|  | } | 
|  | } | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | // Parameterized test suites enable us to run the same tests against each memory | 
|  | // function and its builtin equivalent (or reference implementation when there | 
|  | // is no builtin). We can also run the same tests on any "r" variants that | 
|  | // operate from right to left. | 
|  | class MemCpyTest : public ::testing::TestWithParam<decltype(&ot_memcpy)> {}; | 
|  | class MemCmpTest : public ::testing::TestWithParam<decltype(&ot_memcmp)> {}; | 
|  | class MemSetTest : public ::testing::TestWithParam<decltype(&ot_memset)> {}; | 
|  | class MemChrTest : public ::testing::TestWithParam<decltype(&ot_memchr)> {}; | 
|  |  | 
|  | INSTANTIATE_TEST_SUITE_P(MemCpy, MemCpyTest, | 
|  | ::testing::Values(ot_memcpy, builtin_memcpy_wrapper)); | 
|  | INSTANTIATE_TEST_SUITE_P(MemCmp, MemCmpTest, | 
|  | ::testing::Values(ot_memcmp, builtin_memcmp_wrapper, | 
|  | memrcmp, ref_memrcmp)); | 
|  | INSTANTIATE_TEST_SUITE_P(MemSet, MemSetTest, | 
|  | ::testing::Values(ot_memset, builtin_memset_wrapper)); | 
|  | INSTANTIATE_TEST_SUITE_P(MemChr, MemChrTest, | 
|  | ::testing::Values(ot_memchr, ref_memchr, ot_memrchr, | 
|  | ref_memrchr)); | 
|  |  | 
|  | using ::testing::Each; | 
|  | using ::testing::ElementsAre; | 
|  |  | 
|  | TEST_P(MemCpyTest, Simple) { | 
|  | auto memcpy_func = GetParam(); | 
|  |  | 
|  | static constexpr size_t kLen = 8; | 
|  | std::vector<uint32_t> xs = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | std::vector<uint32_t> ys(kLen); | 
|  | ASSERT_EQ(xs.size(), kLen); | 
|  |  | 
|  | // Copy `xs[:-1]` to `ys[1:]`. | 
|  | memcpy_func(ys.data() + 1, xs.data(), (kLen - 1) * sizeof(uint32_t)); | 
|  | EXPECT_THAT(ys, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); | 
|  |  | 
|  | // Copy `xs[:-1]` to `ys[:-1]`. | 
|  | memcpy_func(ys.data(), xs.data(), (kLen - 1) * sizeof(uint32_t)); | 
|  | EXPECT_THAT(ys, ElementsAre(1, 2, 3, 4, 5, 6, 7, 7)); | 
|  |  | 
|  | // Copy `xs[:-2]` to `ys[2:]`. | 
|  | memcpy_func(&ys[2], xs.data(), (kLen - 2) * sizeof(uint32_t)); | 
|  | EXPECT_THAT(ys, ElementsAre(1, 2, 1, 2, 3, 4, 5, 6)); | 
|  | } | 
|  |  | 
|  | TEST_P(MemCpyTest, VaryingSize) { | 
|  | auto memcpy_func = GetParam(); | 
|  |  | 
|  | static constexpr size_t kLen = 8; | 
|  | static constexpr uint32_t kZeroes[kLen] = {0}; | 
|  | std::vector<uint32_t> xs = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | ASSERT_EQ(xs.size(), kLen); | 
|  |  | 
|  | for (size_t i = 0; i < kLen; ++i) { | 
|  | // Zero out `xs[:i]`. | 
|  | memcpy_func(xs.data(), kZeroes, i * sizeof(uint32_t)); | 
|  |  | 
|  | std::vector<uint32_t> expected = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | std::fill_n(/*first=*/expected.begin(), /*count=*/i, /*value=*/0); | 
|  | EXPECT_EQ(xs, expected) << "i=" << i; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemCpyTest, VarySrcAlignment) { | 
|  | auto memcpy_func = GetParam(); | 
|  |  | 
|  | static constexpr size_t kLen = 8; | 
|  | const std::vector<uint32_t> xs_original = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | ASSERT_EQ(xs_original.size(), kLen); | 
|  |  | 
|  | const std::vector<uint32_t> ys = {11, 12, 13, 14, 15, 16, 17, 18}; | 
|  | ASSERT_EQ(ys.size(), kLen); | 
|  |  | 
|  | for (size_t i = 0; i < kLen; ++i) { | 
|  | SCOPED_TRACE(testing::Message() << "i=" << i); | 
|  |  | 
|  | std::vector<uint32_t> xs(xs_original); | 
|  |  | 
|  | // Copy `ys[i:]` to `xs[:-i]`. | 
|  | const size_t num_u32_to_copy = kLen - i; | 
|  | memcpy_func(xs.data(), &ys[i], num_u32_to_copy * sizeof(uint32_t)); | 
|  |  | 
|  | std::vector<uint32_t> xs_expected(xs_original); | 
|  | for (size_t j = 0; j < num_u32_to_copy; ++j) { | 
|  | xs_expected[j] = 11 + i + j; | 
|  | } | 
|  | EXPECT_EQ(xs, xs_expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemCpyTest, VaryDestAlignment) { | 
|  | auto memcpy_func = GetParam(); | 
|  |  | 
|  | static constexpr size_t kLen = 8; | 
|  | const std::vector<uint32_t> xs_original = {1, 2, 3, 4, 5, 6, 7, 8}; | 
|  | ASSERT_EQ(xs_original.size(), kLen); | 
|  |  | 
|  | const std::vector<uint32_t> ys = {11, 12, 13, 14, 15, 16, 17, 18}; | 
|  | ASSERT_EQ(ys.size(), kLen); | 
|  |  | 
|  | for (size_t i = 0; i < kLen; ++i) { | 
|  | SCOPED_TRACE(testing::Message() << "i=" << i); | 
|  |  | 
|  | std::vector<uint32_t> xs(xs_original); | 
|  |  | 
|  | // Copy `ys[:-i]` to `xs[i:]`. | 
|  | const size_t num_u32_to_copy = kLen - i; | 
|  | memcpy_func(&xs[i], ys.data(), num_u32_to_copy * sizeof(uint32_t)); | 
|  |  | 
|  | std::vector<uint32_t> xs_expected(xs_original); | 
|  | for (size_t j = 0; j < num_u32_to_copy; ++j) { | 
|  | xs_expected[j + i] = 11 + j; | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(xs, xs_expected); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemCmpTest, NullParam) { | 
|  | auto memcmp_func = GetParam(); | 
|  |  | 
|  | const uint8_t data[16] = {0}; | 
|  | EXPECT_EQ(memcmp_func(nullptr, nullptr, 0), 0); | 
|  | EXPECT_EQ(memcmp_func(data, nullptr, 0), 0); | 
|  | EXPECT_EQ(memcmp_func(nullptr, data, 0), 0); | 
|  | EXPECT_EQ(memcmp_func(data, data, 0), 0); | 
|  | } | 
|  |  | 
|  | TEST_P(MemCmpTest, ZeroesVaryingOffsetsAndLengths) { | 
|  | auto memcmp_func = GetParam(); | 
|  |  | 
|  | const uint8_t data[16] = {0}; | 
|  | for (size_t offset = 0; offset <= 8; offset++) { | 
|  | for (size_t len = 0; len < 8; len++) { | 
|  | EXPECT_EQ(memcmp_func(data, data, len), 0); | 
|  | EXPECT_EQ(memcmp_func(data, data + offset, len), 0); | 
|  | EXPECT_EQ(memcmp_func(data + offset, data, len), 0); | 
|  | EXPECT_EQ(memcmp_func(data + offset, data + offset, len), 0); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemSetTest, Null) { | 
|  | auto memset_func = GetParam(); | 
|  |  | 
|  | EXPECT_EQ(memset_func(nullptr, 0, 0), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_P(MemCmpTest, Properties) { | 
|  | auto memcmp_func = GetParam(); | 
|  |  | 
|  | constexpr size_t kLen = 5; | 
|  | constexpr uint8_t xs[kLen] = {0, 0, 0, 0, 0}; | 
|  | constexpr uint8_t ys[kLen] = {0, 0, 0, 1, 3}; | 
|  | constexpr uint8_t zs[kLen] = {0, 0, 0, 1, 4}; | 
|  |  | 
|  | // Reflexive property. | 
|  | EXPECT_EQ(memcmp_func(xs, xs, kLen), 0); | 
|  | EXPECT_EQ(memcmp_func(ys, ys, kLen), 0); | 
|  | EXPECT_EQ(memcmp_func(zs, zs, kLen), 0); | 
|  | // Transitive property for less-than result. | 
|  | EXPECT_LT(memcmp_func(xs, ys, kLen), 0); | 
|  | EXPECT_LT(memcmp_func(ys, zs, kLen), 0); | 
|  | EXPECT_LT(memcmp_func(xs, zs, kLen), 0); | 
|  | // Transitive property for greater-than result. | 
|  | EXPECT_GT(memcmp_func(ys, xs, kLen), 0); | 
|  | EXPECT_GT(memcmp_func(zs, ys, kLen), 0); | 
|  | EXPECT_GT(memcmp_func(zs, xs, kLen), 0); | 
|  | } | 
|  |  | 
|  | TEST_P(MemCmpTest, DoesNotUseSystemEndianness) { | 
|  | auto memcmp_func = GetParam(); | 
|  |  | 
|  | const bool reverse = memcmp_func == &memrcmp || memcmp_func == &ref_memrcmp; | 
|  |  | 
|  | constexpr uint8_t kBuf1[] = {0, 0, 0, 1}; | 
|  | constexpr uint8_t kBuf2[] = {0, 0, 1, 0}; | 
|  |  | 
|  | if (!reverse) { | 
|  | // A lexicographic, byte-wise comparison will see that `kBuf1 < kBuf2`, but | 
|  | // a naive word-wise comparison might think that `kBuf1 > kBuf2` because it | 
|  | // interprets `kBuf1` as 0x01000000 and `kBuf2` as 0x00010000 and compares | 
|  | // `uint32_t` values. | 
|  | EXPECT_LT(memcmp_func(kBuf1, kBuf2, sizeof(kBuf1)), 0); | 
|  | } else { | 
|  | EXPECT_GT(memcmp_func(kBuf1, kBuf2, sizeof(kBuf1)), 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemSetTest, Simple) { | 
|  | auto memset_func = GetParam(); | 
|  |  | 
|  | for (uint32_t len = 0; len < 32; ++len) { | 
|  | SCOPED_TRACE(testing::Message() << "len = " << len); | 
|  |  | 
|  | std::vector<uint32_t> xs(len, 123); | 
|  | EXPECT_THAT(xs, Each(123)); | 
|  | EXPECT_EQ(memset_func(xs.data(), 0, xs.size() * sizeof(uint32_t)), | 
|  | xs.data()); | 
|  | EXPECT_THAT(xs, Each(0)); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemChrTest, Null) { | 
|  | auto memchr_func = GetParam(); | 
|  |  | 
|  | EXPECT_EQ(memchr_func(nullptr, 0, 0), nullptr); | 
|  | EXPECT_EQ(memchr_func(nullptr, 42, 0), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_P(MemChrTest, NonNullButEmpty) { | 
|  | auto memchr_func = GetParam(); | 
|  |  | 
|  | const uint8_t kEmpty[] = {}; | 
|  | static_assert(sizeof(kEmpty) == 0, "kEmpty should contain zero bytes"); | 
|  |  | 
|  | EXPECT_EQ(memchr_func(kEmpty, 0, sizeof(kEmpty)), nullptr); | 
|  | EXPECT_EQ(memchr_func(kEmpty, 42, sizeof(kEmpty)), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_P(MemChrTest, Simple) { | 
|  | auto memchr_func = GetParam(); | 
|  |  | 
|  | std::vector<uint8_t> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; | 
|  | EXPECT_EQ(memchr_func(vec.data(), 0, vec.size()), vec.data()); | 
|  | EXPECT_EQ(memchr_func(vec.data(), 5, vec.size()), vec.data() + 5); | 
|  |  | 
|  | constexpr size_t kLen = 11; | 
|  | constexpr uint8_t data[kLen] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; | 
|  | EXPECT_EQ(memchr_func(data, 0, kLen), data); | 
|  | EXPECT_EQ(memchr_func(data, 1, kLen), data + 1); | 
|  | EXPECT_EQ(memchr_func(data, 4, kLen), data + 4); | 
|  | EXPECT_EQ(memchr_func(data, 8, kLen), data + 8); | 
|  | EXPECT_EQ(memchr_func(data, 42, kLen), nullptr); | 
|  | } | 
|  |  | 
|  | TEST_P(MemChrTest, VaryingLengths) { | 
|  | auto memchr_func = GetParam(); | 
|  |  | 
|  | for (size_t len = 0; len < 16; ++len) { | 
|  | std::vector<uint8_t> vec; | 
|  | vec.reserve(len); | 
|  | for (size_t i = 0; i < len; ++i) { | 
|  | vec.push_back(static_cast<uint8_t>(i)); | 
|  | } | 
|  | // Search for each value. | 
|  | for (size_t i = 0; i < len; ++i) { | 
|  | EXPECT_EQ(memchr_func(vec.data(), i, len), vec.data() + i); | 
|  | } | 
|  | // Search for a value that is not in the vector. | 
|  | EXPECT_EQ(memchr_func(vec.data(), len + 1, len), nullptr); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST_P(MemChrTest, RepeatedBytes) { | 
|  | auto memchr_func = GetParam(); | 
|  |  | 
|  | const bool reverse = | 
|  | memchr_func == &ot_memrchr || memchr_func == &ref_memrchr; | 
|  |  | 
|  | // Define a buffer that contains repeated bytes at a variety of offsets. | 
|  | constexpr uint8_t kBuf[] = {// No repeated bytes yet. | 
|  | 0, 1, 2, 3, | 
|  | // First byte is repeated once. | 
|  | 4, 4, 5, 6, | 
|  | // Second byte is repeated once | 
|  | 7, 8, 8, 9, | 
|  | // Third byte is repeated once. | 
|  | 10, 11, 12, 12, | 
|  | // First byte is repeated three times. | 
|  | 13, 13, 13, 14, | 
|  | // Second byte is repeated three times. | 
|  | 15, 16, 16, 16, | 
|  | // First byte is repeated four times. | 
|  | 17, 17, 17, 17}; | 
|  |  | 
|  | EXPECT_EQ(memchr_func(kBuf, 0, sizeof(kBuf)), kBuf); | 
|  | EXPECT_EQ(memchr_func(kBuf, 1, sizeof(kBuf)), kBuf + 1); | 
|  | EXPECT_EQ(memchr_func(kBuf, 2, sizeof(kBuf)), kBuf + 2); | 
|  | EXPECT_EQ(memchr_func(kBuf, 3, sizeof(kBuf)), kBuf + 3); | 
|  | EXPECT_EQ(memchr_func(kBuf, 4, sizeof(kBuf)), reverse ? kBuf + 5 : kBuf + 4); | 
|  | EXPECT_EQ(memchr_func(kBuf, 5, sizeof(kBuf)), kBuf + 6); | 
|  | EXPECT_EQ(memchr_func(kBuf, 6, sizeof(kBuf)), kBuf + 7); | 
|  | EXPECT_EQ(memchr_func(kBuf, 7, sizeof(kBuf)), kBuf + 8); | 
|  | EXPECT_EQ(memchr_func(kBuf, 8, sizeof(kBuf)), reverse ? kBuf + 10 : kBuf + 9); | 
|  | EXPECT_EQ(memchr_func(kBuf, 9, sizeof(kBuf)), kBuf + 11); | 
|  | EXPECT_EQ(memchr_func(kBuf, 10, sizeof(kBuf)), kBuf + 12); | 
|  | EXPECT_EQ(memchr_func(kBuf, 11, sizeof(kBuf)), kBuf + 13); | 
|  | EXPECT_EQ(memchr_func(kBuf, 12, sizeof(kBuf)), | 
|  | reverse ? kBuf + 15 : kBuf + 14); | 
|  | EXPECT_EQ(memchr_func(kBuf, 13, sizeof(kBuf)), | 
|  | reverse ? kBuf + 18 : kBuf + 16); | 
|  | EXPECT_EQ(memchr_func(kBuf, 14, sizeof(kBuf)), kBuf + 19); | 
|  | EXPECT_EQ(memchr_func(kBuf, 15, sizeof(kBuf)), kBuf + 20); | 
|  | EXPECT_EQ(memchr_func(kBuf, 16, sizeof(kBuf)), | 
|  | reverse ? kBuf + 23 : kBuf + 21); | 
|  | EXPECT_EQ(memchr_func(kBuf, 17, sizeof(kBuf)), | 
|  | reverse ? kBuf + 27 : kBuf + 24); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace memory_unittest |