|
|
@@ -0,0 +1,336 @@ |
|
|
//===-- 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, CopySrcAlignedBlocks) { |
|
|
auto &trace = GetTrace(); |
|
|
// Source is aligned and multiple of alignment. |
|
|
// "1111" |
|
|
trace.Clear(); |
|
|
CopySrcAlignedBlocks<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(); |
|
|
CopySrcAlignedBlocks<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(); |
|
|
CopySrcAlignedBlocks<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(); |
|
|
CopySrcAlignedBlocks<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(); |
|
|
CopySrcAlignedBlocks<4>(I(0), I(1), 11); |
|
|
EXPECT_STREQ(trace.Write(), "11121111111"); |
|
|
EXPECT_STREQ(trace.Read(), "011121111111"); |
|
|
} |
|
|
|
|
|
TEST(LlvmLibcMemcpyUtilsTest, CopyDstAlignedBlocks) { |
|
|
auto &trace = GetTrace(); |
|
|
// Destination is aligned and multiple of alignment. |
|
|
// "1111" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<4>(I(0), I(0), 4); |
|
|
EXPECT_STREQ(trace.Write(), "2222"); |
|
|
EXPECT_STREQ(trace.Read(), "2222"); |
|
|
|
|
|
// Destination is aligned and multiple of alignment. |
|
|
// "11110000" |
|
|
// + "00001111" |
|
|
// = "11111111" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<4>(I(0), I(0), 8); |
|
|
EXPECT_STREQ(trace.Write(), "11111111"); |
|
|
EXPECT_STREQ(trace.Read(), "11111111"); |
|
|
|
|
|
// Destination is aligned already overlap at end. |
|
|
// "1111000000000" |
|
|
// + "0000111100000" |
|
|
// + "0000000011110" |
|
|
// + "0000000001111" |
|
|
// = "1111111112221" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<4>(I(0), I(0), 13); |
|
|
EXPECT_STREQ(trace.Write(), "1111111112221"); |
|
|
EXPECT_STREQ(trace.Read(), "1111111112221"); |
|
|
|
|
|
// Misaligned destination. |
|
|
// "01111000000000" |
|
|
// + "00001111000000" |
|
|
// + "00000000111100" |
|
|
// + "00000000001111" |
|
|
// = "01112111112211" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<4>(I(1), I(0), 13); |
|
|
EXPECT_STREQ(trace.Write(), "01112111112211"); |
|
|
EXPECT_STREQ(trace.Read(), "1112111112211"); |
|
|
|
|
|
// Misaligned destination aligned at end. |
|
|
// "011110000000" |
|
|
// + "000011110000" |
|
|
// + "000000001111" |
|
|
// = "011121111111" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<4>(I(1), I(0), 11); |
|
|
EXPECT_STREQ(trace.Write(), "011121111111"); |
|
|
EXPECT_STREQ(trace.Read(), "11121111111"); |
|
|
} |
|
|
|
|
|
TEST(LlvmLibcMemcpyUtilsTest, CopyAlignedBlocksWithAlignment) { |
|
|
auto &trace = GetTrace(); |
|
|
// Source is aligned and multiple of alignment. |
|
|
// "11111111" |
|
|
trace.Clear(); |
|
|
CopySrcAlignedBlocks<8, 4>(I(0), I(0), 8); |
|
|
EXPECT_STREQ(trace.Write(), "22221111"); |
|
|
EXPECT_STREQ(trace.Read(), "22221111"); |
|
|
|
|
|
// Destination is aligned and multiple of alignment. |
|
|
// "11111111" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<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(); |
|
|
CopySrcAlignedBlocks<8, 4>(I(0), I(0), 9); |
|
|
EXPECT_STREQ(trace.Write(), "122211111"); |
|
|
EXPECT_STREQ(trace.Read(), "122211111"); |
|
|
|
|
|
// Destination is aligned and multiple of alignment. |
|
|
// "111111111" |
|
|
trace.Clear(); |
|
|
CopyDstAlignedBlocks<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. |
|
|
CopySrcAlignedBlocks<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. |
|
|
CopySrcAlignedBlocks<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 |