blob: 7caa2acf3faa98a88febfd1ed8e720980b34819e [file] [log] [blame]
// 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