[libc-commits] [libc] 83f9b13 - [libc] Optimized version of memmove

Guillaume Chatelet via libc-commits libc-commits at lists.llvm.org
Tue Feb 8 03:55:32 PST 2022


Author: Guillaume Chatelet
Date: 2022-02-08T11:55:09Z
New Revision: 83f9b13d8cc2dd148c8ff7c1c71584e1eb634b75

URL: https://github.com/llvm/llvm-project/commit/83f9b13d8cc2dd148c8ff7c1c71584e1eb634b75
DIFF: https://github.com/llvm/llvm-project/commit/83f9b13d8cc2dd148c8ff7c1c71584e1eb634b75.diff

LOG: [libc] Optimized version of memmove

This implementation relies on storing data in registers for sizes up to 128B.
Then depending on whether `dst` is less (resp. greater) than `src` we move data forward (resp. backward) by chunks of 32B.
We first make sure one of the pointers is aligned to increase performance on large move sizes.

Differential Revision: https://reviews.llvm.org/D114637

Added: 
    libc/utils/UnitTest/MemoryMatcher.cpp
    libc/utils/UnitTest/MemoryMatcher.h

Modified: 
    libc/src/__support/CPP/ArrayRef.h
    libc/src/string/CMakeLists.txt
    libc/src/string/memmove.cpp
    libc/src/string/memory_utils/elements.h
    libc/test/src/string/CMakeLists.txt
    libc/test/src/string/memmove_test.cpp
    libc/utils/UnitTest/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/CPP/ArrayRef.h b/libc/src/__support/CPP/ArrayRef.h
index a9a09101a6a80..d6833e6969ef4 100644
--- a/libc/src/__support/CPP/ArrayRef.h
+++ b/libc/src/__support/CPP/ArrayRef.h
@@ -131,6 +131,10 @@ struct MutableArrayRef : public internal::ArrayRefBase<T> {
 public:
   // From Array.
   template <size_t N> MutableArrayRef(Array<T, N> &Arr) : Impl(Arr.Data, N) {}
+
+  operator ArrayRef<T>() const {
+    return ArrayRef<T>(this->data(), this->size());
+  }
 };
 
 } // namespace cpp

diff  --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt
index 4bd7f12151f13..ac29785574434 100644
--- a/libc/src/string/CMakeLists.txt
+++ b/libc/src/string/CMakeLists.txt
@@ -409,7 +409,6 @@ function(add_memmove memmove_name)
     HDRS ${LIBC_SOURCE_DIR}/src/string/memmove.h
     DEPENDS
       .memory_utils.memory_utils
-      .memory_utils.memcpy_implementation
       libc.include.string
     COMPILE_OPTIONS
       -fno-builtin

diff  --git a/libc/src/string/memmove.cpp b/libc/src/string/memmove.cpp
index ce411fd1641a6..f24257893b20c 100644
--- a/libc/src/string/memmove.cpp
+++ b/libc/src/string/memmove.cpp
@@ -10,59 +10,41 @@
 
 #include "src/__support/common.h"
 #include "src/__support/integer_operations.h"
-#include "src/string/memory_utils/memcpy_implementations.h"
+#include "src/string/memory_utils/elements.h"
 #include <stddef.h> // size_t, ptr
diff _t
 
 namespace __llvm_libc {
 
-static inline void move_byte_forward(char *dest_m, const char *src_m,
-                                     size_t count) {
-  for (size_t offset = 0; count; --count, ++offset)
-    dest_m[offset] = src_m[offset];
-}
-
-static inline void move_byte_backward(char *dest_m, const char *src_m,
-                                      size_t count) {
-  for (size_t offset = count - 1; count; --count, --offset)
-    dest_m[offset] = src_m[offset];
+static inline void inline_memmove(char *dst, const char *src, size_t count) {
+  using namespace __llvm_libc::scalar;
+  if (count == 0)
+    return;
+  if (count == 1)
+    return move<_1>(dst, src);
+  if (count <= 4)
+    return move<HeadTail<_2>>(dst, src, count);
+  if (count <= 8)
+    return move<HeadTail<_4>>(dst, src, count);
+  if (count <= 16)
+    return move<HeadTail<_8>>(dst, src, count);
+  if (count <= 32)
+    return move<HeadTail<_16>>(dst, src, count);
+  if (count <= 64)
+    return move<HeadTail<_32>>(dst, src, count);
+  if (count <= 128)
+    return move<HeadTail<_64>>(dst, src, count);
+
+  using AlignedMoveLoop = Align<_16, Arg::Src>::Then<Loop<_64>>;
+  if (dst < src)
+    return move<AlignedMoveLoop>(dst, src, count);
+  else if (dst > src)
+    return move_backward<AlignedMoveLoop>(dst, src, count);
 }
 
 LLVM_LIBC_FUNCTION(void *, memmove,
                    (void *dst, const void *src, size_t count)) {
-  char *dest_c = reinterpret_cast<char *>(dst);
-  const char *src_c = reinterpret_cast<const char *>(src);
-
-  // If the distance between `src_c` and `dest_c` is equal to or greater
-  // than `count` (integerAbs(src_c - dest_c) >= count), they would not overlap.
-  // e.g.   greater     equal       overlapping
-  //        [12345678]  [12345678]  [12345678]
-  // src_c: [_ab_____]  [_ab_____]  [_ab_____]
-  // dest_c:[_____yz_]  [___yz___]  [__yz____]
-
-  // Call `memcpy` if `src_c` and `dest_c` do not overlap.
-  if (__llvm_libc::integer_abs(src_c - dest_c) >=
-      static_cast<ptr
diff _t>(count)) {
-    inline_memcpy(dest_c, src_c, count);
-    return dest_c;
-  }
-
-  // Overlapping cases.
-  // If `dest_c` starts before `src_c` (dest_c < src_c), copy
-  // forward(pointer add 1) from beginning to end.
-  // If `dest_c` starts after `src_c` (dest_c > src_c), copy
-  // backward(pointer add -1) from end to beginning.
-  // If `dest_c` and `src_c` start at the same address (dest_c == src_c),
-  // just return dest.
-  // e.g.    forward      backward
-  //                *->    <-*
-  // src_c : [___abcde_]  [_abcde___]
-  // dest_c: [_abc--___]  [___--cde_]
-
-  // TODO: Optimize `move_byte_xxx(...)` functions.
-  if (dest_c < src_c)
-    move_byte_forward(dest_c, src_c, count);
-  if (dest_c > src_c)
-    move_byte_backward(dest_c, src_c, count);
+  inline_memmove(reinterpret_cast<char *>(dst),
+                 reinterpret_cast<const char *>(src), count);
   return dst;
 }
 

diff  --git a/libc/src/string/memory_utils/elements.h b/libc/src/string/memory_utils/elements.h
index b10a744751c33..68718cd607170 100644
--- a/libc/src/string/memory_utils/elements.h
+++ b/libc/src/string/memory_utils/elements.h
@@ -43,6 +43,11 @@ template <typename Element> void move(char *dst, const char *src) {
 template <typename Element> void move(char *dst, const char *src, size_t size) {
   Element::move(dst, src, size);
 }
+// Runtime-size move from 'src' to 'dst'.
+template <typename Element>
+void move_backward(char *dst, const char *src, size_t size) {
+  Element::move_backward(dst, src, size);
+}
 
 // Fixed-size equality between 'lhs' and 'rhs'.
 template <typename Element> bool equals(const char *lhs, const char *rhs) {
@@ -96,10 +101,8 @@ template <typename Element, size_t ElementCount> struct Repeated {
   }
 
   static void move(char *dst, const char *src) {
-    const auto value = Element::load(src);
-    Repeated<Element, ElementCount - 1>::move(dst + Element::SIZE,
-                                              src + Element::SIZE);
-    Element::store(dst, value);
+    const auto value = load(src);
+    store(dst, value);
   }
 
   static bool equals(const char *lhs, const char *rhs) {
@@ -341,6 +344,55 @@ template <typename T, typename TailT = T> struct Loop {
     Tail<TailT>::copy(dst, src, size);
   }
 
+  // Move forward suitable when dst < src. We load the tail bytes before
+  // handling the loop.
+  //
+  // e.g. Moving two bytes
+  // [   |       |       |       |       |]
+  // [___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX___]
+  // [_________________________LLLLLLLL___]
+  // [___LLLLLLLL_________________________]
+  // [_SSSSSSSS___________________________]
+  // [___________LLLLLLLL_________________]
+  // [_________SSSSSSSS___________________]
+  // [___________________LLLLLLLL_________]
+  // [_________________SSSSSSSS___________]
+  // [_______________________SSSSSSSS_____]
+  static void move(char *dst, const char *src, size_t size) {
+    const size_t tail_offset = Tail<T>::offset(size);
+    const auto tail_value = TailT::load(src + tail_offset);
+    size_t offset = 0;
+    do {
+      T::move(dst + offset, src + offset);
+      offset += T::SIZE;
+    } while (offset < size - T::SIZE);
+    TailT::store(dst + tail_offset, tail_value);
+  }
+
+  // Move forward suitable when dst > src. We load the head bytes before
+  // handling the loop.
+  //
+  // e.g. Moving two bytes
+  // [   |       |       |       |       |]
+  // [___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX___]
+  // [___LLLLLLLL_________________________]
+  // [_________________________LLLLLLLL___]
+  // [___________________________SSSSSSSS_]
+  // [_________________LLLLLLLL___________]
+  // [___________________SSSSSSSS_________]
+  // [_________LLLLLLLL___________________]
+  // [___________SSSSSSSS_________________]
+  // [_____SSSSSSSS_______________________]
+  static void move_backward(char *dst, const char *src, size_t size) {
+    const auto head_value = TailT::load(src);
+    ptr
diff _t offset = size - T::SIZE;
+    do {
+      T::move(dst + offset, src + offset);
+      offset -= T::SIZE;
+    } while (offset >= 0);
+    TailT::store(dst, head_value);
+  }
+
   static bool equals(const char *lhs, const char *rhs, size_t size) {
     size_t offset = 0;
     do {
@@ -375,30 +427,38 @@ enum class Arg { _1, _2, Dst = _1, Src = _2, Lhs = _1, Rhs = _2 };
 
 namespace internal {
 
-// Provides a specialized bump function that adjusts pointers and size so first
-// argument (resp. second argument) gets aligned to Alignment.
-// We make sure the compiler knows about the adjusted pointer alignment.
-template <Arg arg, size_t Alignment> struct AlignHelper {};
+template <Arg arg> struct ArgSelector {};
 
-template <size_t Alignment> struct AlignHelper<Arg::_1, Alignment> {
+template <> struct ArgSelector<Arg::_1> {
   template <typename T1, typename T2>
-  static void bump(T1 *__restrict &p1ref, T2 *__restrict &p2ref, size_t &size) {
-    const intptr_t offset = offset_to_next_aligned<Alignment>(p1ref);
-    p1ref += offset;
-    p2ref += offset;
-    size -= offset;
-    p1ref = assume_aligned<Alignment>(p1ref);
+  static T1 *__restrict &Select(T1 *__restrict &p1ref, T2 *__restrict &p2ref) {
+    return p1ref;
+  }
+};
+
+template <> struct ArgSelector<Arg::_2> {
+  template <typename T1, typename T2>
+  static T2 *__restrict &Select(T1 *__restrict &p1ref, T2 *__restrict &p2ref) {
+    return p2ref;
   }
 };
 
-template <size_t Alignment> struct AlignHelper<Arg::_2, Alignment> {
+// Provides a specialized bump function that adjusts pointers and size so first
+// argument (resp. second argument) gets aligned to Alignment.
+// We make sure the compiler knows about the adjusted pointer alignment.
+// The 'additional_bumps' parameter allows to reach previous / next aligned
+// pointers.
+template <Arg arg, size_t Alignment> struct Align {
   template <typename T1, typename T2>
-  static void bump(T1 *__restrict &p1ref, T2 *__restrict &p2ref, size_t &size) {
-    const intptr_t offset = offset_to_next_aligned<Alignment>(p2ref);
+  static void bump(T1 *__restrict &p1ref, T2 *__restrict &p2ref, size_t &size,
+                   int additional_bumps = 0) {
+    auto &aligned_ptr = ArgSelector<arg>::Select(p1ref, p2ref);
+    auto offset = offset_to_next_aligned<Alignment>(aligned_ptr);
+    offset += additional_bumps * Alignment;
     p1ref += offset;
     p2ref += offset;
     size -= offset;
-    p2ref = assume_aligned<Alignment>(p2ref);
+    aligned_ptr = assume_aligned<Alignment>(aligned_ptr);
   }
 };
 
@@ -423,14 +483,70 @@ template <typename AlignmentT, Arg AlignOn = Arg::_1> struct Align {
     static void copy(char *__restrict dst, const char *__restrict src,
                      size_t size) {
       AlignmentT::copy(dst, src);
-      internal::AlignHelper<AlignOn, ALIGNMENT>::bump(dst, src, size);
+      internal::Align<AlignOn, ALIGNMENT>::bump(dst, src, size);
       NextT::copy(dst, src, size);
     }
 
+    // Move forward suitable when dst < src. The alignment is performed with an
+    // HeadTail operation of size ∈ [Alignment, 2 x Alignment].
+    //
+    // e.g. Moving two bytes and making sure src is then aligned.
+    // [  |       |       |       |      ]
+    // [____XXXXXXXXXXXXXXXXXXXXXXXXXXXX_]
+    // [____LLLLLLLL_____________________]
+    // [___________LLLLLLLL______________]
+    // [_SSSSSSSS________________________]
+    // [________SSSSSSSS_________________]
+    //
+    // e.g. Moving two bytes and making sure dst is then aligned.
+    // [  |       |       |       |      ]
+    // [____XXXXXXXXXXXXXXXXXXXXXXXXXXXX_]
+    // [____LLLLLLLL_____________________]
+    // [______LLLLLLLL___________________]
+    // [_SSSSSSSS________________________]
+    // [___SSSSSSSS______________________]
+    static void move(char *dst, const char *src, size_t size) {
+      char *next_dst = dst;
+      const char *next_src = src;
+      size_t next_size = size;
+      internal::Align<AlignOn, ALIGNMENT>::bump(next_dst, next_src, next_size,
+                                                1);
+      HeadTail<AlignmentT>::move(dst, src, size - next_size);
+      NextT::move(next_dst, next_src, next_size);
+    }
+
+    // Move backward suitable when dst > src. The alignment is performed with an
+    // HeadTail operation of size ∈ [Alignment, 2 x Alignment].
+    //
+    // e.g. Moving two bytes backward and making sure src is then aligned.
+    // [  |       |       |       |      ]
+    // [____XXXXXXXXXXXXXXXXXXXXXXXX_____]
+    // [ _________________LLLLLLLL_______]
+    // [ ___________________LLLLLLLL_____]
+    // [____________________SSSSSSSS_____]
+    // [______________________SSSSSSSS___]
+    //
+    // e.g. Moving two bytes and making sure dst is then aligned.
+    // [  |       |       |       |      ]
+    // [____XXXXXXXXXXXXXXXXXXXXXXXX_____]
+    // [ _______________LLLLLLLL_________]
+    // [ ___________________LLLLLLLL_____]
+    // [__________________SSSSSSSS_______]
+    // [______________________SSSSSSSS___]
+    static void move_backward(char *dst, const char *src, size_t size) {
+      char *headtail_dst = dst + size;
+      const char *headtail_src = src + size;
+      size_t headtail_size = 0;
+      internal::Align<AlignOn, ALIGNMENT>::bump(headtail_dst, headtail_src,
+                                                headtail_size, -2);
+      HeadTail<AlignmentT>::move(headtail_dst, headtail_src, headtail_size);
+      NextT::move_backward(dst, src, size - headtail_size);
+    }
+
     static bool equals(const char *lhs, const char *rhs, size_t size) {
       if (!AlignmentT::equals(lhs, rhs))
         return false;
-      internal::AlignHelper<AlignOn, ALIGNMENT>::bump(lhs, rhs, size);
+      internal::Align<AlignOn, ALIGNMENT>::bump(lhs, rhs, size);
       return NextT::equals(lhs, rhs, size);
     }
 
@@ -438,14 +554,14 @@ template <typename AlignmentT, Arg AlignOn = Arg::_1> struct Align {
                                  size_t size) {
       if (!AlignmentT::equals(lhs, rhs))
         return AlignmentT::three_way_compare(lhs, rhs);
-      internal::AlignHelper<AlignOn, ALIGNMENT>::bump(lhs, rhs, size);
+      internal::Align<AlignOn, ALIGNMENT>::bump(lhs, rhs, size);
       return NextT::three_way_compare(lhs, rhs, size);
     }
 
     static void splat_set(char *dst, const unsigned char value, size_t size) {
       AlignmentT::splat_set(dst, value);
       char *dummy = nullptr;
-      internal::AlignHelper<Arg::_1, ALIGNMENT>::bump(dst, dummy, size);
+      internal::Align<Arg::_1, ALIGNMENT>::bump(dst, dummy, size);
       NextT::splat_set(dst, value, size);
     }
   };

diff  --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt
index 6713bd73b7242..4152290f6b848 100644
--- a/libc/test/src/string/CMakeLists.txt
+++ b/libc/test/src/string/CMakeLists.txt
@@ -261,6 +261,8 @@ function(add_libc_multi_impl_test name)
           ${LIBC_COMPILE_OPTIONS_NATIVE}
         ${ARGN}
       )
+      get_fq_target_name(${fq_config_name}_test fq_target_name)
+      target_link_libraries(${fq_target_name} PRIVATE LibcMemoryHelpers)
     else()
       message(STATUS "Skipping test for '${fq_config_name}' insufficient host cpu features '${required_cpu_features}'")
     endif()

diff  --git a/libc/test/src/string/memmove_test.cpp b/libc/test/src/string/memmove_test.cpp
index b922fb4d5dd44..cacd2bf2132d9 100644
--- a/libc/test/src/string/memmove_test.cpp
+++ b/libc/test/src/string/memmove_test.cpp
@@ -8,64 +8,106 @@
 
 #include "src/__support/CPP/ArrayRef.h"
 #include "src/string/memmove.h"
+#include "utils/UnitTest/MemoryMatcher.h"
 #include "utils/UnitTest/Test.h"
 
-class LlvmLibcMemmoveTest : public __llvm_libc::testing::Test {
-public:
-  void check_memmove(void *dst, const void *src, size_t count,
-                     const unsigned char *str,
-                     const __llvm_libc::cpp::ArrayRef<unsigned char> expected) {
-    void *result = __llvm_libc::memmove(dst, src, count);
-    // Making sure the pointer returned is same with `dst`.
-    EXPECT_EQ(result, dst);
-    // `expected` is designed according to `str`.
-    // `dst` and `src` might be part of `str`.
-    // Making sure `str` is same with `expected`.
-    for (size_t i = 0; i < expected.size(); ++i)
-      EXPECT_EQ(str[i], expected[i]);
-  }
-};
+using __llvm_libc::cpp::Array;
+using __llvm_libc::cpp::ArrayRef;
+using __llvm_libc::cpp::MutableArrayRef;
 
-TEST_F(LlvmLibcMemmoveTest, MoveZeroByte) {
-  unsigned char dst[] = {'a', 'b'};
-  const unsigned char src[] = {'y', 'z'};
-  const unsigned char expected[] = {'a', 'b'};
-  check_memmove(dst, src, 0, dst, expected);
+TEST(LlvmLibcMemmoveTest, MoveZeroByte) {
+  char Buffer[] = {'a', 'b', 'y', 'z'};
+  const char Expected[] = {'a', 'b', 'y', 'z'};
+  void *const Dst = Buffer;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer + 2, 0);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
 }
 
-TEST_F(LlvmLibcMemmoveTest, OverlapThatDstAndSrcPointToSameAddress) {
-  unsigned char str[] = {'a', 'b'};
-  const unsigned char expected[] = {'a', 'b'};
-  check_memmove(str, str, 1, str, expected);
+TEST(LlvmLibcMemmoveTest, DstAndSrcPointToSameAddress) {
+  char Buffer[] = {'a', 'b'};
+  const char Expected[] = {'a', 'b'};
+  void *const Dst = Buffer;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer, 1);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
 }
 
-TEST_F(LlvmLibcMemmoveTest, OverlapThatDstStartsBeforeSrc) {
+TEST(LlvmLibcMemmoveTest, DstStartsBeforeSrc) {
   // Set boundary at beginning and end for not overstepping when
   // copy forward or backward.
-  unsigned char str[] = {'z', 'a', 'b', 'c', 'z'};
-  const unsigned char expected[] = {'z', 'b', 'c', 'c', 'z'};
-  // `dst` is `&str[1]`.
-  check_memmove(&str[1], &str[2], 2, str, expected);
+  char Buffer[] = {'z', 'a', 'b', 'c', 'z'};
+  const char Expected[] = {'z', 'b', 'c', 'c', 'z'};
+  void *const Dst = Buffer + 1;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer + 2, 2);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
 }
 
-TEST_F(LlvmLibcMemmoveTest, OverlapThatDstStartsAfterSrc) {
-  unsigned char str[] = {'z', 'a', 'b', 'c', 'z'};
-  const unsigned char expected[] = {'z', 'a', 'a', 'b', 'z'};
-  check_memmove(&str[2], &str[1], 2, str, expected);
+TEST(LlvmLibcMemmoveTest, DstStartsAfterSrc) {
+  char Buffer[] = {'z', 'a', 'b', 'c', 'z'};
+  const char Expected[] = {'z', 'a', 'a', 'b', 'z'};
+  void *const Dst = Buffer + 2;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer + 1, 2);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
 }
 
-// e.g. `dst` follow `src`.
+// e.g. `Dst` follow `src`.
 // str: [abcdefghij]
 //      [__src_____]
-//      [_____dst__]
-TEST_F(LlvmLibcMemmoveTest, SrcFollowDst) {
-  unsigned char str[] = {'z', 'a', 'b', 'z'};
-  const unsigned char expected[] = {'z', 'b', 'b', 'z'};
-  check_memmove(&str[1], &str[2], 1, str, expected);
+//      [_____Dst__]
+TEST(LlvmLibcMemmoveTest, SrcFollowDst) {
+  char Buffer[] = {'z', 'a', 'b', 'z'};
+  const char Expected[] = {'z', 'b', 'b', 'z'};
+  void *const Dst = Buffer + 1;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer + 2, 1);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
+}
+
+TEST(LlvmLibcMemmoveTest, DstFollowSrc) {
+  char Buffer[] = {'z', 'a', 'b', 'z'};
+  const char Expected[] = {'z', 'a', 'a', 'z'};
+  void *const Dst = Buffer + 2;
+  void *const Ret = __llvm_libc::memmove(Dst, Buffer + 1, 1);
+  EXPECT_EQ(Ret, Dst);
+  EXPECT_MEM_EQ(Buffer, Expected);
 }
 
-TEST_F(LlvmLibcMemmoveTest, DstFollowSrc) {
-  unsigned char str[] = {'z', 'a', 'b', 'z'};
-  const unsigned char expected[] = {'z', 'a', 'a', 'z'};
-  check_memmove(&str[2], &str[1], 1, str, expected);
+static constexpr int kMaxSize = 512;
+
+char GetRandomChar() {
+  static constexpr const uint64_t A = 1103515245;
+  static constexpr const uint64_t C = 12345;
+  static constexpr const uint64_t M = 1ULL << 31;
+  static uint64_t Seed = 123456789;
+  Seed = (A * Seed + C) % M;
+  return Seed;
+}
+
+void Randomize(MutableArrayRef<char> Buffer) {
+  for (auto &current : Buffer)
+    current = GetRandomChar();
+}
+
+TEST(LlvmLibcMemmoveTest, Thorough) {
+  using LargeBuffer = Array<char, 3 * kMaxSize>;
+  LargeBuffer GroundTruth;
+  Randomize(GroundTruth);
+  for (int Size = 0; Size < kMaxSize; ++Size) {
+    for (int Offset = -Size; Offset < Size; ++Offset) {
+      LargeBuffer Buffer = GroundTruth;
+      LargeBuffer Expected = GroundTruth;
+      size_t DstOffset = kMaxSize;
+      size_t SrcOffset = kMaxSize + Offset;
+      for (int I = 0; I < Size; ++I)
+        Expected[DstOffset + I] = GroundTruth[SrcOffset + I];
+      void *const Dst = Buffer.data() + DstOffset;
+      void *const Ret =
+          __llvm_libc::memmove(Dst, Buffer.data() + SrcOffset, Size);
+      EXPECT_EQ(Ret, Dst);
+      EXPECT_MEM_EQ(Buffer, Expected);
+    }
+  }
 }

diff  --git a/libc/utils/UnitTest/CMakeLists.txt b/libc/utils/UnitTest/CMakeLists.txt
index 2870fb2553f45..9d53d39a01e0d 100644
--- a/libc/utils/UnitTest/CMakeLists.txt
+++ b/libc/utils/UnitTest/CMakeLists.txt
@@ -32,3 +32,16 @@ add_dependencies(
   libc.src.__support.CPP.standalone_cpp
   libc.src.__support.FPUtil.fputil
 )
+
+add_library(
+  LibcMemoryHelpers
+    MemoryMatcher.h
+    MemoryMatcher.cpp
+)
+target_include_directories(LibcMemoryHelpers PUBLIC ${LIBC_SOURCE_DIR})
+target_link_libraries(LibcMemoryHelpers LibcUnitTest)
+add_dependencies(
+  LibcMemoryHelpers
+  LibcUnitTest
+  libc.src.__support.CPP.standalone_cpp
+)

diff  --git a/libc/utils/UnitTest/MemoryMatcher.cpp b/libc/utils/UnitTest/MemoryMatcher.cpp
new file mode 100644
index 0000000000000..195516a7d7b29
--- /dev/null
+++ b/libc/utils/UnitTest/MemoryMatcher.cpp
@@ -0,0 +1,46 @@
+//===-- MemoryMatcher.cpp ---------------------------------------*- C++ -*-===//
+//
+// 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 "MemoryMatcher.h"
+
+namespace __llvm_libc {
+namespace memory {
+namespace testing {
+
+bool MemoryMatcher::match(MemoryView actualValue) {
+  actual = actualValue;
+  return expected.equals(actual);
+}
+
+void display(testutils::StreamWrapper &Stream, char C) {
+  const auto print = [&Stream](unsigned char I) {
+    Stream << static_cast<char>(I < 10 ? '0' + I : 'A' + I - 10);
+  };
+  print(static_cast<unsigned char>(C) / 16);
+  print(static_cast<unsigned char>(C) & 15);
+}
+
+void display(testutils::StreamWrapper &Stream, MemoryView View) {
+  for (auto C : View) {
+    Stream << ' ';
+    display(Stream, C);
+  }
+}
+
+void MemoryMatcher::explainError(testutils::StreamWrapper &Stream) {
+  Stream << "expected :";
+  display(Stream, expected);
+  Stream << '\n';
+  Stream << "actual   :";
+  display(Stream, actual);
+  Stream << '\n';
+}
+
+} // namespace testing
+} // namespace memory
+} // namespace __llvm_libc

diff  --git a/libc/utils/UnitTest/MemoryMatcher.h b/libc/utils/UnitTest/MemoryMatcher.h
new file mode 100644
index 0000000000000..ea39ecb1c27aa
--- /dev/null
+++ b/libc/utils/UnitTest/MemoryMatcher.h
@@ -0,0 +1,41 @@
+//===-- MemoryMatcher.h -----------------------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_UTILS_UNITTEST_MEMORY_MATCHER_H
+#define LLVM_LIBC_UTILS_UNITTEST_MEMORY_MATCHER_H
+
+#include "src/__support/CPP/ArrayRef.h"
+
+#include "utils/UnitTest/Test.h"
+
+namespace __llvm_libc {
+namespace memory {
+namespace testing {
+
+using MemoryView = __llvm_libc::cpp::ArrayRef<char>;
+
+class MemoryMatcher : public __llvm_libc::testing::Matcher<MemoryView> {
+  MemoryView expected;
+  MemoryView actual;
+
+public:
+  MemoryMatcher(MemoryView expectedValue) : expected(expectedValue) {}
+
+  bool match(MemoryView actualValue);
+
+  void explainError(testutils::StreamWrapper &stream) override;
+};
+
+} // namespace testing
+} // namespace memory
+} // namespace __llvm_libc
+
+#define EXPECT_MEM_EQ(expected, actual)                                        \
+  EXPECT_THAT(actual, __llvm_libc::memory::testing::MemoryMatcher(expected))
+
+#endif // LLVM_LIBC_UTILS_UNITTEST_MEMORY_MATCHER_H


        


More information about the libc-commits mailing list