| //===-- Unittests for memory_utils ----------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/string/memory_utils/memcpy_utils.h" |
| #include "utils/CPP/Array.h" |
| #include "utils/UnitTest/Test.h" |
| |
| #include <assert.h> |
| #include <stdint.h> // uintptr_t |
| |
| #ifndef LLVM_LIBC_MEMCPY_MONITOR |
| #error LLVM_LIBC_MEMCPY_MONITOR must be defined for this test. |
| #endif |
| |
| namespace __llvm_libc { |
| |
| struct Buffer { |
| static constexpr size_t kMaxBuffer = 1024; |
| char buffer[kMaxBuffer + 1]; |
| size_t last = 0; |
| |
| void Clear() { |
| last = 0; |
| for (size_t i = 0; i < kMaxBuffer; ++i) |
| buffer[i] = '0'; |
| buffer[kMaxBuffer] = '\0'; |
| } |
| |
| void Increment(const void *ptr) { |
| const auto offset = reinterpret_cast<uintptr_t>(ptr); |
| assert(offset < kMaxBuffer); |
| ++buffer[offset]; |
| if (offset > last) |
| last = offset; |
| } |
| |
| char *Finish() { |
| assert(last < kMaxBuffer); |
| buffer[last + 1] = '\0'; |
| return buffer; |
| } |
| }; |
| |
| struct Trace { |
| Buffer read; |
| Buffer write; |
| |
| void Add(char *__restrict dst, const char *__restrict src, size_t count) { |
| for (size_t i = 0; i < count; ++i) |
| read.Increment(src + i); |
| for (size_t i = 0; i < count; ++i) |
| write.Increment(dst + i); |
| } |
| |
| void Clear() { |
| read.Clear(); |
| write.Clear(); |
| } |
| |
| char *Read() { return read.Finish(); } |
| char *Write() { return write.Finish(); } |
| }; |
| |
| static Trace &GetTrace() { |
| static thread_local Trace events; |
| return events; |
| } |
| |
| extern "C" void LLVM_LIBC_MEMCPY_MONITOR(char *__restrict dst, |
| const char *__restrict src, |
| size_t count) { |
| GetTrace().Add(dst, src, count); |
| } |
| |
| char *I(uintptr_t offset) { return reinterpret_cast<char *>(offset); } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyTrivial) { |
| auto &trace = GetTrace(); |
| |
| trace.Clear(); |
| CopyBlock<1>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "1"); |
| EXPECT_STREQ(trace.Read(), "1"); |
| |
| trace.Clear(); |
| CopyBlock<2>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "11"); |
| EXPECT_STREQ(trace.Read(), "11"); |
| |
| trace.Clear(); |
| CopyBlock<4>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "1111"); |
| EXPECT_STREQ(trace.Read(), "1111"); |
| |
| trace.Clear(); |
| CopyBlock<8>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "11111111"); |
| EXPECT_STREQ(trace.Read(), "11111111"); |
| |
| trace.Clear(); |
| CopyBlock<16>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "1111111111111111"); |
| EXPECT_STREQ(trace.Read(), "1111111111111111"); |
| |
| trace.Clear(); |
| CopyBlock<32>(I(0), I(0)); |
| EXPECT_STREQ(trace.Write(), "11111111111111111111111111111111"); |
| EXPECT_STREQ(trace.Read(), "11111111111111111111111111111111"); |
| |
| trace.Clear(); |
| CopyBlock<64>(I(0), I(0)); |
| EXPECT_STREQ( |
| trace.Write(), |
| "1111111111111111111111111111111111111111111111111111111111111111"); |
| EXPECT_STREQ( |
| trace.Read(), |
| "1111111111111111111111111111111111111111111111111111111111111111"); |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyOffset) { |
| auto &trace = GetTrace(); |
| |
| trace.Clear(); |
| CopyBlock<1>(I(3), I(1)); |
| EXPECT_STREQ(trace.Write(), "0001"); |
| EXPECT_STREQ(trace.Read(), "01"); |
| |
| trace.Clear(); |
| CopyBlock<1>(I(2), I(1)); |
| EXPECT_STREQ(trace.Write(), "001"); |
| EXPECT_STREQ(trace.Read(), "01"); |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyBlockOverlap) { |
| auto &trace = GetTrace(); |
| |
| trace.Clear(); |
| CopyBlockOverlap<2>(I(0), I(0), 2); |
| EXPECT_STREQ(trace.Write(), "22"); |
| EXPECT_STREQ(trace.Read(), "22"); |
| |
| trace.Clear(); |
| CopyBlockOverlap<2>(I(0), I(0), 3); |
| EXPECT_STREQ(trace.Write(), "121"); |
| EXPECT_STREQ(trace.Read(), "121"); |
| |
| trace.Clear(); |
| CopyBlockOverlap<2>(I(0), I(0), 4); |
| EXPECT_STREQ(trace.Write(), "1111"); |
| EXPECT_STREQ(trace.Read(), "1111"); |
| |
| trace.Clear(); |
| CopyBlockOverlap<4>(I(2), I(1), 7); |
| EXPECT_STREQ(trace.Write(), "001112111"); |
| EXPECT_STREQ(trace.Read(), "01112111"); |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyAlignedBlocks) { |
| auto &trace = GetTrace(); |
| // Source is aligned and multiple of alignment. |
| // "1111" |
| trace.Clear(); |
| CopyAlignedBlocks<4>(I(0), I(0), 4); |
| EXPECT_STREQ(trace.Write(), "2222"); |
| EXPECT_STREQ(trace.Read(), "2222"); |
| |
| // Source is aligned and multiple of alignment. |
| // "11110000" |
| // + "00001111" |
| // = "11111111" |
| trace.Clear(); |
| CopyAlignedBlocks<4>(I(0), I(0), 8); |
| EXPECT_STREQ(trace.Write(), "11111111"); |
| EXPECT_STREQ(trace.Read(), "11111111"); |
| |
| // Source is aligned already overlap at end. |
| // "1111000000000" |
| // + "0000111100000" |
| // + "0000000011110" |
| // + "0000000001111" |
| // = "1111111112221" |
| trace.Clear(); |
| CopyAlignedBlocks<4>(I(0), I(0), 13); |
| EXPECT_STREQ(trace.Write(), "1111111112221"); |
| EXPECT_STREQ(trace.Read(), "1111111112221"); |
| |
| // Misaligned source. |
| // "01111000000000" |
| // + "00001111000000" |
| // + "00000000111100" |
| // + "00000000001111" |
| // = "01112111112211" |
| trace.Clear(); |
| CopyAlignedBlocks<4>(I(0), I(1), 13); |
| EXPECT_STREQ(trace.Write(), "1112111112211"); |
| EXPECT_STREQ(trace.Read(), "01112111112211"); |
| |
| // Misaligned source aligned at end. |
| // "011110000000" |
| // + "000011110000" |
| // + "000000001111" |
| // = "011121111111" |
| trace.Clear(); |
| CopyAlignedBlocks<4>(I(0), I(1), 11); |
| EXPECT_STREQ(trace.Write(), "11121111111"); |
| EXPECT_STREQ(trace.Read(), "011121111111"); |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyAlignedBlocksWithAlignment) { |
| auto &trace = GetTrace(); |
| // Source is aligned and multiple of alignment. |
| // "11111111" |
| trace.Clear(); |
| CopyAlignedBlocks<8, 4>(I(0), I(0), 8); |
| EXPECT_STREQ(trace.Write(), "22221111"); |
| EXPECT_STREQ(trace.Read(), "22221111"); |
| |
| // Source is aligned and multiple of alignment. |
| // "111111111" |
| trace.Clear(); |
| CopyAlignedBlocks<8, 4>(I(0), I(0), 9); |
| EXPECT_STREQ(trace.Write(), "122211111"); |
| EXPECT_STREQ(trace.Read(), "122211111"); |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyAlignedBlocksMaxReloads) { |
| auto &trace = GetTrace(); |
| for (size_t alignment = 0; alignment < 32; ++alignment) { |
| for (size_t count = 64; count < 768; ++count) { |
| trace.Clear(); |
| // We should never reload more than twice when copying from count = 2x32. |
| CopyAlignedBlocks<32>(I(alignment), I(0), count); |
| const char *const written = trace.Write(); |
| // First bytes are untouched. |
| for (size_t i = 0; i < alignment; ++i) |
| EXPECT_EQ(written[i], '0'); |
| // Next bytes are loaded once or twice but no more. |
| for (size_t i = alignment; i < count; ++i) { |
| EXPECT_GE(written[i], '1'); |
| EXPECT_LE(written[i], '2'); |
| } |
| } |
| } |
| } |
| |
| TEST(LlvmLibcMemcpyUtilsTest, CopyAlignedBlocksWithAlignmentMaxReloads) { |
| auto &trace = GetTrace(); |
| for (size_t alignment = 0; alignment < 32; ++alignment) { |
| for (size_t count = 64; count < 768; ++count) { |
| trace.Clear(); |
| // We should never reload more than twice when copying from count = 2x32. |
| CopyAlignedBlocks<32, 16>(I(alignment), I(0), count); |
| const char *const written = trace.Write(); |
| // First bytes are untouched. |
| for (size_t i = 0; i < alignment; ++i) |
| EXPECT_EQ(written[i], '0'); |
| // Next bytes are loaded once or twice but no more. |
| for (size_t i = alignment; i < count; ++i) { |
| EXPECT_GE(written[i], '1'); |
| EXPECT_LE(written[i], '2'); |
| } |
| } |
| } |
| } |
| |
| } // namespace __llvm_libc |