[libc-commits] [libc] 3635195 - [libc] Improve testing of mem functions
Guillaume Chatelet via libc-commits
libc-commits at lists.llvm.org
Wed Nov 2 01:56:01 PDT 2022
Author: Guillaume Chatelet
Date: 2022-11-02T08:55:46Z
New Revision: 3635195e0d54162ba85124fdefcac39bff703149
URL: https://github.com/llvm/llvm-project/commit/3635195e0d54162ba85124fdefcac39bff703149
DIFF: https://github.com/llvm/llvm-project/commit/3635195e0d54162ba85124fdefcac39bff703149.diff
LOG: [libc] Improve testing of mem functions
This patch extracts the testing logic from `op_tests.cpp` into
`memory_check_utils.h` so we can reuse it for mem* function integration
tests.
This makes testing consistent and thorough.
For instance this catches a bug that got unnoticed during submission of
D136595 and D135134. Integration test for memcmp was only testing a
single size.
This also leverages ASAN to make sure that data is not read / written
outside permitted boundaries
Differential Revision: https://reviews.llvm.org/D136865
Added:
libc/test/src/string/memory_utils/memory_check_utils.h
Modified:
libc/test/src/string/bcmp_test.cpp
libc/test/src/string/bzero_test.cpp
libc/test/src/string/memcmp_test.cpp
libc/test/src/string/memcpy_test.cpp
libc/test/src/string/memmove_test.cpp
libc/test/src/string/memory_utils/op_tests.cpp
libc/test/src/string/memset_test.cpp
Removed:
################################################################################
diff --git a/libc/test/src/string/bcmp_test.cpp b/libc/test/src/string/bcmp_test.cpp
index 8f0fe5262f22d..5f0bb0fc932fa 100644
--- a/libc/test/src/string/bcmp_test.cpp
+++ b/libc/test/src/string/bcmp_test.cpp
@@ -6,9 +6,12 @@
//
//===----------------------------------------------------------------------===//
+#include "memory_utils/memory_check_utils.h"
#include "src/string/bcmp.h"
#include "utils/UnitTest/Test.h"
+namespace __llvm_libc {
+
TEST(LlvmLibcBcmpTest, CmpZeroByte) {
const char *lhs = "ab";
const char *rhs = "bc";
@@ -33,26 +36,23 @@ TEST(LlvmLibcBcmpTest, LhsAfterRhsLexically) {
ASSERT_NE(__llvm_libc::bcmp(lhs, rhs, 2), 0);
}
-TEST(LlvmLibcBcmpTest, Sweep) {
- static constexpr size_t K_MAX_SIZE = 1024;
- char lhs[K_MAX_SIZE];
- char rhs[K_MAX_SIZE];
-
- const auto reset = [](char *const ptr) {
- for (size_t i = 0; i < K_MAX_SIZE; ++i)
- ptr[i] = 'a';
- };
-
- reset(lhs);
- reset(rhs);
- for (size_t i = 0; i < K_MAX_SIZE; ++i)
- ASSERT_EQ(__llvm_libc::bcmp(lhs, rhs, i), 0);
-
- reset(lhs);
- reset(rhs);
- for (size_t i = 0; i < K_MAX_SIZE; ++i) {
- rhs[i] = 'b';
- ASSERT_NE(__llvm_libc::bcmp(lhs, rhs, K_MAX_SIZE), 0);
- rhs[i] = 'a';
+// Adapt CheckBcmp signature to op implementation signatures.
+template <auto FnImpl>
+int CmpAdaptor(cpp::span<char> p1, cpp::span<char> p2, size_t size) {
+ return FnImpl(p1.begin(), p2.begin(), size);
+}
+
+TEST(LlvmLibcBcmpTest, SizeSweep) {
+ static constexpr size_t kMaxSize = 1024;
+ static constexpr auto Impl = CmpAdaptor<__llvm_libc::bcmp>;
+ Buffer Buffer1(kMaxSize);
+ Buffer Buffer2(kMaxSize);
+ Randomize(Buffer1.span());
+ for (size_t size = 0; size < kMaxSize; ++size) {
+ auto span1 = Buffer1.span().subspan(0, size);
+ auto span2 = Buffer2.span().subspan(0, size);
+ ASSERT_TRUE((CheckBcmp<Impl>(span1, span2, size)));
}
}
+
+} // namespace __llvm_libc
diff --git a/libc/test/src/string/bzero_test.cpp b/libc/test/src/string/bzero_test.cpp
index 5ae3a497ab2ca..1f4be711edc44 100644
--- a/libc/test/src/string/bzero_test.cpp
+++ b/libc/test/src/string/bzero_test.cpp
@@ -6,44 +6,27 @@
//
//===----------------------------------------------------------------------===//
-#include "src/__support/CPP/span.h"
+#include "memory_utils/memory_check_utils.h"
#include "src/string/bzero.h"
#include "utils/UnitTest/Test.h"
-using __llvm_libc::cpp::array;
-using __llvm_libc::cpp::span;
-using Data = array<char, 2048>;
+namespace __llvm_libc {
-static const span<const char> k_deadcode("DEADC0DE", 8);
-
-// Returns a Data object filled with a repetition of `filler`.
-Data get_data(span<const char> filler) {
- Data out;
- for (size_t i = 0; i < out.size(); ++i)
- out[i] = filler[i % filler.size()];
- return out;
+// Adapt CheckMemset signature to op implementation signatures.
+template <auto FnImpl>
+void BzeroAdaptor(cpp::span<char> p1, uint8_t value, size_t size) {
+ assert(value == 0);
+ FnImpl(p1.begin(), size);
}
-TEST(LlvmLibcBzeroTest, Thorough) {
- const Data dirty = get_data(k_deadcode);
- for (size_t count = 0; count < 1024; ++count) {
- for (size_t align = 0; align < 64; ++align) {
- auto buffer = dirty;
- char *const dst = &buffer[align];
- __llvm_libc::bzero(dst, count);
- // Everything before copy is untouched.
- for (size_t i = 0; i < align; ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- // Everything in between is copied.
- for (size_t i = 0; i < count; ++i)
- ASSERT_EQ(buffer[align + i], char(0));
- // Everything after copy is untouched.
- for (size_t i = align + count; i < dirty.size(); ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- }
+TEST(LlvmLibcBzeroTest, SizeSweep) {
+ static constexpr size_t kMaxSize = 1024;
+ static constexpr auto Impl = BzeroAdaptor<__llvm_libc::bzero>;
+ Buffer DstBuffer(kMaxSize);
+ for (size_t size = 0; size < kMaxSize; ++size) {
+ auto dst = DstBuffer.span().subspan(0, size);
+ ASSERT_TRUE((CheckMemset<Impl>(dst, 0, size)));
}
}
-// FIXME: Add tests with reads and writes on the boundary of a read/write
-// protected page to check we're not reading nor writing prior/past the allowed
-// regions.
+} // namespace __llvm_libc
diff --git a/libc/test/src/string/memcmp_test.cpp b/libc/test/src/string/memcmp_test.cpp
index ab798012342e8..9056851add960 100644
--- a/libc/test/src/string/memcmp_test.cpp
+++ b/libc/test/src/string/memcmp_test.cpp
@@ -6,9 +6,12 @@
//
//===----------------------------------------------------------------------===//
+#include "memory_utils/memory_check_utils.h"
#include "src/string/memcmp.h"
#include "utils/UnitTest/Test.h"
+namespace __llvm_libc {
+
TEST(LlvmLibcMemcmpTest, CmpZeroByte) {
const char *lhs = "ab";
const char *rhs = "yz";
@@ -33,26 +36,23 @@ TEST(LlvmLibcMemcmpTest, LhsAfterRhsLexically) {
EXPECT_GT(__llvm_libc::memcmp(lhs, rhs, 2), 0);
}
-TEST(LlvmLibcMemcmpTest, Sweep) {
- static constexpr size_t K_MAX_SIZE = 1024;
- char lhs[K_MAX_SIZE];
- char rhs[K_MAX_SIZE];
-
- const auto reset = [](char *const ptr) {
- for (size_t i = 0; i < K_MAX_SIZE; ++i)
- ptr[i] = 'a';
- };
-
- reset(lhs);
- reset(rhs);
- for (size_t i = 0; i < K_MAX_SIZE; ++i)
- ASSERT_EQ(__llvm_libc::memcmp(lhs, rhs, i), 0);
-
- reset(lhs);
- reset(rhs);
- for (size_t i = 0; i < K_MAX_SIZE; ++i) {
- rhs[i] = 'z';
- ASSERT_LT(__llvm_libc::memcmp(lhs, rhs, K_MAX_SIZE), 0);
- rhs[i] = 'a';
+// Adapt CheckMemcmp signature to op implementation signatures.
+template <auto FnImpl>
+int CmpAdaptor(cpp::span<char> p1, cpp::span<char> p2, size_t size) {
+ return FnImpl(p1.begin(), p2.begin(), size);
+}
+
+TEST(LlvmLibcMemcmpTest, SizeSweep) {
+ static constexpr size_t kMaxSize = 1024;
+ static constexpr auto Impl = CmpAdaptor<__llvm_libc::memcmp>;
+ Buffer Buffer1(kMaxSize);
+ Buffer Buffer2(kMaxSize);
+ Randomize(Buffer1.span());
+ for (size_t size = 0; size < kMaxSize; ++size) {
+ auto span1 = Buffer1.span().subspan(0, size);
+ auto span2 = Buffer2.span().subspan(0, size);
+ ASSERT_TRUE((CheckMemcmp<Impl>(span1, span2, size)));
}
}
+
+} // namespace __llvm_libc
diff --git a/libc/test/src/string/memcpy_test.cpp b/libc/test/src/string/memcpy_test.cpp
index 5ccbb4dc45598..99631630f2645 100644
--- a/libc/test/src/string/memcpy_test.cpp
+++ b/libc/test/src/string/memcpy_test.cpp
@@ -6,49 +6,29 @@
//
//===----------------------------------------------------------------------===//
-#include "src/__support/CPP/span.h"
+#include "memory_utils/memory_check_utils.h"
#include "src/string/memcpy.h"
#include "utils/UnitTest/Test.h"
-using __llvm_libc::cpp::array;
-using __llvm_libc::cpp::span;
-using Data = array<char, 2048>;
+namespace __llvm_libc {
-static const span<const char> k_numbers("0123456789", 10);
-static const span<const char> k_deadcode("DEADC0DE", 8);
-
-// Returns a Data object filled with a repetition of `filler`.
-Data get_data(span<const char> filler) {
- Data out;
- for (size_t i = 0; i < out.size(); ++i)
- out[i] = filler[i % filler.size()];
- return out;
+// Adapt CheckMemcpy signature to op implementation signatures.
+template <auto FnImpl>
+void CopyAdaptor(cpp::span<char> dst, cpp::span<char> src, size_t size) {
+ FnImpl(dst.begin(), src.begin(), size);
}
-TEST(LlvmLibcMemcpyTest, Thorough) {
- const Data groundtruth = get_data(k_numbers);
- const Data dirty = get_data(k_deadcode);
- for (size_t count = 0; count < 1024; ++count) {
- for (size_t align = 0; align < 64; ++align) {
- auto buffer = dirty;
- const char *const src = groundtruth.data();
- void *const dst = &buffer[align];
- void *const ret = __llvm_libc::memcpy(dst, src, count);
- // Return value is `dst`.
- ASSERT_EQ(ret, dst);
- // Everything before copy is untouched.
- for (size_t i = 0; i < align; ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- // Everything in between is copied.
- for (size_t i = 0; i < count; ++i)
- ASSERT_EQ(buffer[align + i], groundtruth[i]);
- // Everything after copy is untouched.
- for (size_t i = align + count; i < dirty.size(); ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- }
+TEST(LlvmLibcMemcpyTest, SizeSweep) {
+ static constexpr size_t kMaxSize = 1024;
+ static constexpr auto Impl = CopyAdaptor<__llvm_libc::memcpy>;
+ Buffer SrcBuffer(kMaxSize);
+ Buffer DstBuffer(kMaxSize);
+ Randomize(SrcBuffer.span());
+ for (size_t size = 0; size < kMaxSize; ++size) {
+ auto src = SrcBuffer.span().subspan(0, size);
+ auto dst = DstBuffer.span().subspan(0, size);
+ ASSERT_TRUE(CheckMemcpy<Impl>(dst, src, size));
}
}
-// FIXME: Add tests with reads and writes on the boundary of a read/write
-// protected page to check we're not reading nor writing prior/past the allowed
-// regions.
+} // namespace __llvm_libc
diff --git a/libc/test/src/string/memmove_test.cpp b/libc/test/src/string/memmove_test.cpp
index 451ccdb0a89b9..26c193620b453 100644
--- a/libc/test/src/string/memmove_test.cpp
+++ b/libc/test/src/string/memmove_test.cpp
@@ -90,7 +90,7 @@ void Randomize(span<char> Buffer) {
current = GetRandomChar();
}
-TEST(LlvmLibcMemmoveTest, Thorough) {
+TEST(LlvmLibcMemmoveTest, SizeSweep) {
using LargeBuffer = array<char, 3 * kMaxSize>;
LargeBuffer GroundTruth;
Randomize(GroundTruth);
diff --git a/libc/test/src/string/memory_utils/memory_check_utils.h b/libc/test/src/string/memory_utils/memory_check_utils.h
new file mode 100644
index 0000000000000..19cd01045bcb8
--- /dev/null
+++ b/libc/test/src/string/memory_utils/memory_check_utils.h
@@ -0,0 +1,171 @@
+//===-- Utils to test conformance of mem functions ------------------------===//
+//
+// 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 LIBC_TEST_SRC_STRING_MEMORY_UTILS_MEMORY_CHECK_UTILS_H
+#define LIBC_TEST_SRC_STRING_MEMORY_UTILS_MEMORY_CHECK_UTILS_H
+
+#include "src/__support/CPP/span.h"
+#include "src/string/memory_utils/utils.h"
+#include <assert.h> // assert
+#include <stddef.h> // size_t
+#include <stdint.h> // uintxx_t
+#include <stdlib.h> // malloc/free
+
+#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
+#include <sanitizer/asan_interface.h>
+#define ASAN_POISON_MEMORY_REGION(addr, size) \
+ __asan_poison_memory_region((addr), (size))
+#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
+ __asan_unpoison_memory_region((addr), (size))
+#else
+#define ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
+#define ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
+#endif
+
+namespace __llvm_libc {
+
+// Simple structure to allocate a buffer of a particular size.
+// When ASAN is present it also poisons the whole memory.
+// This is a utility class to be used by Buffer below, do not use directly.
+struct PoisonedBuffer {
+ PoisonedBuffer(size_t size) : ptr((char *)malloc(size)) {
+ assert(ptr);
+ ASAN_POISON_MEMORY_REGION(ptr, size);
+ }
+ ~PoisonedBuffer() { free(ptr); }
+
+protected:
+ char *ptr = nullptr;
+};
+
+// Simple structure to allocate a buffer (aligned or not) of a particular size.
+// It is backed by a wider buffer that is marked poisoned when ASAN is present.
+// The requested region is unpoisoned, this allows catching out of bounds
+// accesses.
+enum class Aligned : bool { NO = false, YES = true };
+struct Buffer : private PoisonedBuffer {
+ static constexpr size_t kAlign = 64;
+ static constexpr size_t kLeeway = 2 * kAlign;
+ Buffer(size_t size, Aligned aligned = Aligned::YES)
+ : PoisonedBuffer(size + kLeeway), size(size) {
+ offset_ptr = ptr;
+ offset_ptr += distance_to_next_aligned<kAlign>(ptr);
+ assert((uintptr_t)(offset_ptr) % kAlign == 0);
+ if (aligned == Aligned::NO)
+ ++offset_ptr;
+ assert(offset_ptr > ptr);
+ assert((offset_ptr + size) < (ptr + size + kLeeway));
+ ASAN_UNPOISON_MEMORY_REGION(offset_ptr, size);
+ }
+ cpp::span<char> span() { return cpp::span<char>(offset_ptr, size); }
+
+private:
+ size_t size = 0;
+ char *offset_ptr = nullptr;
+};
+
+static inline 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;
+}
+
+// Randomize the content of the buffer.
+static inline void Randomize(cpp::span<char> buffer) {
+ for (auto ¤t : buffer)
+ current = GetRandomChar();
+}
+
+// Copy one span to another.
+__attribute__((no_builtin)) static inline void
+ReferenceCopy(cpp::span<char> dst, const cpp::span<char> src) {
+ assert(dst.size() == src.size());
+ for (size_t i = 0; i < dst.size(); ++i)
+ dst[i] = src[i];
+}
+
+// Checks that FnImpl implements the memcpy semantic.
+template <auto FnImpl>
+bool CheckMemcpy(cpp::span<char> dst, cpp::span<char> src, size_t size) {
+ assert(dst.size() == src.size());
+ assert(dst.size() == size);
+ Randomize(dst);
+ FnImpl(dst, src, size);
+ for (size_t i = 0; i < size; ++i)
+ if (dst[i] != src[i])
+ return false;
+ return true;
+}
+
+// Checks that FnImpl implements the memset semantic.
+template <auto FnImpl>
+bool CheckMemset(cpp::span<char> dst, uint8_t value, size_t size) {
+ Randomize(dst);
+ FnImpl(dst, value, size);
+ for (char c : dst)
+ if (c != (char)value)
+ return false;
+ return true;
+}
+
+// Checks that FnImpl implements the bcmp semantic.
+template <auto FnImpl>
+bool CheckBcmp(cpp::span<char> span1, cpp::span<char> span2, size_t size) {
+ assert(span1.size() == span2.size());
+ ReferenceCopy(span2, span1);
+ // Compare equal
+ if (int cmp = FnImpl(span1, span2, size); cmp != 0)
+ return false;
+ // Compare not equal if any byte
diff ers
+ for (size_t i = 0; i < size; ++i) {
+ ++span2[i];
+ if (int cmp = FnImpl(span1, span2, size); cmp == 0)
+ return false;
+ if (int cmp = FnImpl(span2, span1, size); cmp == 0)
+ return false;
+ --span2[i];
+ }
+ return true;
+}
+
+// Checks that FnImpl implements the memcmp semantic.
+template <auto FnImpl>
+bool CheckMemcmp(cpp::span<char> span1, cpp::span<char> span2, size_t size) {
+ assert(span1.size() == span2.size());
+ ReferenceCopy(span2, span1);
+ // Compare equal
+ if (int cmp = FnImpl(span1, span2, size); cmp != 0)
+ return false;
+ // Compare not equal if any byte
diff ers
+ for (size_t i = 0; i < size; ++i) {
+ ++span2[i];
+ int ground_truth = __builtin_memcmp(span1.data(), span2.data(), size);
+ if (ground_truth > 0) {
+ if (int cmp = FnImpl(span1, span2, size); cmp <= 0)
+ return false;
+ if (int cmp = FnImpl(span2, span1, size); cmp >= 0)
+ return false;
+ } else {
+ if (int cmp = FnImpl(span1, span2, size); cmp >= 0)
+ return false;
+ if (int cmp = FnImpl(span2, span1, size); cmp <= 0)
+ return false;
+ }
+ --span2[i];
+ }
+ return true;
+}
+
+// TODO: Also implement the memmove semantic
+
+} // namespace __llvm_libc
+
+#endif // LIBC_TEST_SRC_STRING_MEMORY_UTILS_MEMORY_CHECK_UTILS_H
diff --git a/libc/test/src/string/memory_utils/op_tests.cpp b/libc/test/src/string/memory_utils/op_tests.cpp
index 715c61152e1be..fdfe9d92ce096 100644
--- a/libc/test/src/string/memory_utils/op_tests.cpp
+++ b/libc/test/src/string/memory_utils/op_tests.cpp
@@ -6,29 +6,14 @@
//
//===----------------------------------------------------------------------===//
-#include "src/__support/CPP/limits.h"
-#include "src/__support/CPP/span.h"
+#include "memory_check_utils.h"
#include "src/string/memory_utils/op_aarch64.h"
#include "src/string/memory_utils/op_builtin.h"
#include "src/string/memory_utils/op_generic.h"
#include "src/string/memory_utils/op_x86.h"
-#include "src/string/memory_utils/utils.h"
#include "utils/UnitTest/Test.h"
#include <assert.h>
-#include <stdlib.h>
-
-// User code should use macros instead of functions.
-#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
-#include <sanitizer/asan_interface.h>
-#define ASAN_POISON_MEMORY_REGION(addr, size) \
- __asan_poison_memory_region((addr), (size))
-#define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
- __asan_unpoison_memory_region((addr), (size))
-#else
-#define ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
-#define ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
-#endif
#if defined(LLVM_LIBC_ARCH_X86_64) || defined(LLVM_LIBC_ARCH_AARCH64)
#define LLVM_LIBC_HAS_UINT64
@@ -36,70 +21,6 @@
namespace __llvm_libc {
-static 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;
-}
-
-// Randomize the content of the buffer.
-static void Randomize(cpp::span<char> buffer) {
- for (auto ¤t : buffer)
- current = GetRandomChar();
-}
-
-// Copy one span to another.
-static void Copy(cpp::span<char> dst, const cpp::span<char> src) {
- assert(dst.size() == src.size());
- for (size_t i = 0; i < dst.size(); ++i)
- dst[i] = src[i];
-}
-
-cpp::byte *as_byte(cpp::span<char> span) {
- return reinterpret_cast<cpp::byte *>(span.data());
-}
-
-// Simple structure to allocate a buffer of a particular size.
-struct PoisonedBuffer {
- PoisonedBuffer(size_t size) : ptr((char *)malloc(size)) {
- assert(ptr);
- ASAN_POISON_MEMORY_REGION(ptr, size);
- }
- ~PoisonedBuffer() { free(ptr); }
-
-protected:
- char *ptr = nullptr;
-};
-
-// Simple structure to allocate a buffer (aligned or not) of a particular size.
-// It is backed by a wider buffer that is marked poisoned when ASAN is present.
-// The requested region is unpoisoned, this allows catching out of bounds
-// accesses.
-enum class Aligned : bool { NO = false, YES = true };
-struct Buffer : private PoisonedBuffer {
- static constexpr size_t kAlign = 64;
- static constexpr size_t kLeeway = 2 * kAlign;
- Buffer(size_t size, Aligned aligned = Aligned::YES)
- : PoisonedBuffer(size + kLeeway), size(size) {
- offset_ptr = ptr;
- offset_ptr += distance_to_next_aligned<kAlign>(ptr);
- assert((uintptr_t)(offset_ptr) % kAlign == 0);
- if (aligned == Aligned::NO)
- ++offset_ptr;
- assert(offset_ptr > ptr);
- assert((offset_ptr + size) < (ptr + size + kLeeway));
- ASAN_UNPOISON_MEMORY_REGION(offset_ptr, size);
- }
- cpp::span<char> span() { return cpp::span<char>(offset_ptr, size); }
-
-private:
- size_t size = 0;
- char *offset_ptr = nullptr;
-};
-
// Allocates two Buffer and extracts two spans out of them, one
// aligned and one misaligned. Tests are run on both spans.
struct Buffers {
@@ -130,56 +51,57 @@ using MemcpyImplementations = testing::TypeList<
#endif // LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
>;
+// Convenient helper to turn a span into cpp::byte *.
+static inline cpp::byte *as_byte(cpp::span<char> span) {
+ return reinterpret_cast<cpp::byte *>(span.data());
+}
+
+// Adapt CheckMemcpy signature to op implementation signatures.
template <auto FnImpl>
-bool CheckMemcpy(cpp::span<char> dst, cpp::span<char> src, size_t size) {
- assert(dst.size() == src.size());
- assert(dst.size() == size);
- Randomize(dst);
+void CopyAdaptor(cpp::span<char> dst, cpp::span<char> src, size_t size) {
FnImpl(as_byte(dst), as_byte(src), size);
- for (size_t i = 0; i < size; ++i)
- if (dst[i] != src[i])
- return false;
- return true;
}
-
-template <typename T>
-static void MemcpyAdaptor(Ptr dst, CPtr src, size_t size) {
- assert(size == T::SIZE);
- return T::block(dst, src);
+template <size_t Size, auto FnImpl>
+void CopyBlockAdaptor(cpp::span<char> dst, cpp::span<char> src, size_t size) {
+ assert(size == Size);
+ FnImpl(as_byte(dst), as_byte(src));
}
TYPED_TEST(LlvmLibcOpTest, Memcpy, MemcpyImplementations) {
using Impl = ParamType;
constexpr size_t kSize = Impl::SIZE;
{ // Test block operation
+ static constexpr auto BlockImpl = CopyBlockAdaptor<kSize, Impl::block>;
Buffers SrcBuffer(kSize);
Buffers DstBuffer(kSize);
for (auto src : SrcBuffer.spans()) {
Randomize(src);
for (auto dst : DstBuffer.spans()) {
- ASSERT_TRUE(CheckMemcpy<MemcpyAdaptor<Impl>>(dst, src, kSize));
+ ASSERT_TRUE(CheckMemcpy<BlockImpl>(dst, src, kSize));
}
}
}
{ // Test head tail operations from kSize to 2 * kSize.
+ static constexpr auto HeadTailImpl = CopyAdaptor<Impl::head_tail>;
Buffer SrcBuffer(2 * kSize);
Buffer DstBuffer(2 * kSize);
Randomize(SrcBuffer.span());
for (size_t size = kSize; size < 2 * kSize; ++size) {
auto src = SrcBuffer.span().subspan(0, size);
auto dst = DstBuffer.span().subspan(0, size);
- ASSERT_TRUE(CheckMemcpy<Impl::head_tail>(dst, src, size));
+ ASSERT_TRUE(CheckMemcpy<HeadTailImpl>(dst, src, size));
}
}
{ // Test loop operations from kSize to 3 * kSize.
if constexpr (kSize > 1) {
+ static constexpr auto LoopImpl = CopyAdaptor<Impl::loop_and_tail>;
Buffer SrcBuffer(3 * kSize);
Buffer DstBuffer(3 * kSize);
Randomize(SrcBuffer.span());
for (size_t size = kSize; size < 3 * kSize; ++size) {
auto src = SrcBuffer.span().subspan(0, size);
auto dst = DstBuffer.span().subspan(0, size);
- ASSERT_TRUE(CheckMemcpy<Impl::loop_and_tail>(dst, src, size));
+ ASSERT_TRUE(CheckMemcpy<LoopImpl>(dst, src, size));
}
}
}
@@ -217,48 +139,46 @@ using MemsetImplementations = testing::TypeList<
generic::Memset<64, 32> //
>;
+// Adapt CheckMemset signature to op implementation signatures.
template <auto FnImpl>
-bool CheckMemset(cpp::span<char> dst, uint8_t value, size_t size) {
- Randomize(dst);
+void SetAdaptor(cpp::span<char> dst, uint8_t value, size_t size) {
FnImpl(as_byte(dst), value, size);
- for (char c : dst)
- if (c != (char)value)
- return false;
- return true;
}
-
-template <typename T>
-static void MemsetAdaptor(Ptr dst, uint8_t value, size_t size) {
- assert(size == T::SIZE);
- return T::block(dst, value);
+template <size_t Size, auto FnImpl>
+void SetBlockAdaptor(cpp::span<char> dst, uint8_t value, size_t size) {
+ assert(size == Size);
+ FnImpl(as_byte(dst), value);
}
TYPED_TEST(LlvmLibcOpTest, Memset, MemsetImplementations) {
using Impl = ParamType;
constexpr size_t kSize = Impl::SIZE;
{ // Test block operation
+ static constexpr auto BlockImpl = SetBlockAdaptor<kSize, Impl::block>;
Buffers DstBuffer(kSize);
for (uint8_t value : cpp::array<uint8_t, 3>{0, 1, 255}) {
for (auto dst : DstBuffer.spans()) {
- ASSERT_TRUE(CheckMemset<MemsetAdaptor<Impl>>(dst, value, kSize));
+ ASSERT_TRUE(CheckMemset<BlockImpl>(dst, value, kSize));
}
}
}
{ // Test head tail operations from kSize to 2 * kSize.
+ static constexpr auto HeadTailImpl = SetAdaptor<Impl::head_tail>;
Buffer DstBuffer(2 * kSize);
for (size_t size = kSize; size < 2 * kSize; ++size) {
const char value = size % 10;
auto dst = DstBuffer.span().subspan(0, size);
- ASSERT_TRUE(CheckMemset<Impl::head_tail>(dst, value, size));
+ ASSERT_TRUE(CheckMemset<HeadTailImpl>(dst, value, size));
}
}
{ // Test loop operations from kSize to 3 * kSize.
if constexpr (kSize > 1) {
+ static constexpr auto LoopImpl = SetAdaptor<Impl::loop_and_tail>;
Buffer DstBuffer(3 * kSize);
for (size_t size = kSize; size < 3 * kSize; ++size) {
const char value = size % 10;
auto dst = DstBuffer.span().subspan(0, size);
- ASSERT_TRUE((CheckMemset<Impl::loop_and_tail>(dst, value, size)));
+ ASSERT_TRUE((CheckMemset<LoopImpl>(dst, value, size)));
}
}
}
@@ -295,62 +215,51 @@ using BcmpImplementations = testing::TypeList<
generic::Bcmp<64> //
>;
+// Adapt CheckBcmp signature to op implementation signatures.
template <auto FnImpl>
-bool CheckBcmp(cpp::span<char> span1, cpp::span<char> span2, size_t size) {
- assert(span1.size() == span2.size());
- Copy(span2, span1);
- // Compare equal
- if (int cmp = (int)FnImpl(as_byte(span1), as_byte(span2), size); cmp != 0)
- return false;
- // Compare not equal if any byte
diff ers
- for (size_t i = 0; i < size; ++i) {
- ++span2[i];
- if (int cmp = (int)FnImpl(as_byte(span1), as_byte(span2), size); cmp == 0)
- return false;
- if (int cmp = (int)FnImpl(as_byte(span2), as_byte(span1), size); cmp == 0)
- return false;
- --span2[i];
- }
- return true;
+int CmpAdaptor(cpp::span<char> p1, cpp::span<char> p2, size_t size) {
+ return (int)FnImpl(as_byte(p1), as_byte(p2), size);
}
-
-template <typename T>
-static BcmpReturnType BcmpAdaptor(CPtr p1, CPtr p2, size_t size) {
- assert(size == T::SIZE);
- return T::block(p1, p2);
+template <size_t Size, auto FnImpl>
+int CmpBlockAdaptor(cpp::span<char> p1, cpp::span<char> p2, size_t size) {
+ assert(size == Size);
+ return (int)FnImpl(as_byte(p1), as_byte(p2));
}
TYPED_TEST(LlvmLibcOpTest, Bcmp, BcmpImplementations) {
using Impl = ParamType;
constexpr size_t kSize = Impl::SIZE;
{ // Test block operation
+ static constexpr auto BlockImpl = CmpBlockAdaptor<kSize, Impl::block>;
Buffers Buffer1(kSize);
Buffers Buffer2(kSize);
for (auto span1 : Buffer1.spans()) {
Randomize(span1);
for (auto span2 : Buffer2.spans())
- ASSERT_TRUE((CheckBcmp<BcmpAdaptor<Impl>>(span1, span2, kSize)));
+ ASSERT_TRUE((CheckBcmp<BlockImpl>(span1, span2, kSize)));
}
}
{ // Test head tail operations from kSize to 2 * kSize.
+ static constexpr auto HeadTailImpl = CmpAdaptor<Impl::head_tail>;
Buffer Buffer1(2 * kSize);
Buffer Buffer2(2 * kSize);
Randomize(Buffer1.span());
for (size_t size = kSize; size < 2 * kSize; ++size) {
auto span1 = Buffer1.span().subspan(0, size);
auto span2 = Buffer2.span().subspan(0, size);
- ASSERT_TRUE((CheckBcmp<Impl::head_tail>(span1, span2, size)));
+ ASSERT_TRUE((CheckBcmp<HeadTailImpl>(span1, span2, size)));
}
}
{ // Test loop operations from kSize to 3 * kSize.
if constexpr (kSize > 1) {
+ static constexpr auto LoopImpl = CmpAdaptor<Impl::loop_and_tail>;
Buffer Buffer1(3 * kSize);
Buffer Buffer2(3 * kSize);
Randomize(Buffer1.span());
for (size_t size = kSize; size < 3 * kSize; ++size) {
auto span1 = Buffer1.span().subspan(0, size);
auto span2 = Buffer2.span().subspan(0, size);
- ASSERT_TRUE((CheckBcmp<Impl::loop_and_tail>(span1, span2, size)));
+ ASSERT_TRUE((CheckBcmp<LoopImpl>(span1, span2, size)));
}
}
}
@@ -384,70 +293,40 @@ using MemcmpImplementations = testing::TypeList<
generic::Memcmp<64> //
>;
-template <auto FnImpl>
-bool CheckMemcmp(cpp::span<char> span1, cpp::span<char> span2, size_t size) {
- assert(span1.size() == span2.size());
- Copy(span2, span1);
- // Compare equal
- if (int cmp = (int)FnImpl(as_byte(span1), as_byte(span2), size); cmp != 0)
- return false;
- // Compare not equal if any byte
diff ers
- for (size_t i = 0; i < size; ++i) {
- ++span2[i];
- int ground_truth = __builtin_memcmp(span1.data(), span2.data(), size);
- if (ground_truth > 0) {
- if (int cmp = (int)FnImpl(as_byte(span1), as_byte(span2), size); cmp <= 0)
- return false;
- if (int cmp = (int)FnImpl(as_byte(span2), as_byte(span1), size); cmp >= 0)
- return false;
- } else {
- if (int cmp = (int)FnImpl(as_byte(span1), as_byte(span2), size); cmp >= 0)
- return false;
- if (int cmp = (int)FnImpl(as_byte(span2), as_byte(span1), size); cmp <= 0)
- return false;
- }
- --span2[i];
- }
- return true;
-}
-
-template <typename T>
-static MemcmpReturnType MemcmpAdaptor(CPtr p1, CPtr p2, size_t size) {
- assert(size == T::SIZE);
- return T::block(p1, p2);
-}
-
TYPED_TEST(LlvmLibcOpTest, Memcmp, MemcmpImplementations) {
using Impl = ParamType;
constexpr size_t kSize = Impl::SIZE;
{ // Test block operation
+ static constexpr auto BlockImpl = CmpBlockAdaptor<kSize, Impl::block>;
Buffers Buffer1(kSize);
Buffers Buffer2(kSize);
for (auto span1 : Buffer1.spans()) {
Randomize(span1);
for (auto span2 : Buffer2.spans())
- ASSERT_TRUE((CheckMemcmp<MemcmpAdaptor<Impl>>(span1, span2, kSize)));
+ ASSERT_TRUE((CheckMemcmp<BlockImpl>(span1, span2, kSize)));
}
}
{ // Test head tail operations from kSize to 2 * kSize.
+ static constexpr auto HeadTailImpl = CmpAdaptor<Impl::head_tail>;
Buffer Buffer1(2 * kSize);
Buffer Buffer2(2 * kSize);
Randomize(Buffer1.span());
for (size_t size = kSize; size < 2 * kSize; ++size) {
auto span1 = Buffer1.span().subspan(0, size);
auto span2 = Buffer2.span().subspan(0, size);
- ASSERT_TRUE((CheckMemcmp<Impl::head_tail>(span1, span2, size)));
+ ASSERT_TRUE((CheckMemcmp<HeadTailImpl>(span1, span2, size)));
}
}
{ // Test loop operations from kSize to 3 * kSize.
if constexpr (kSize > 1) {
+ static constexpr auto LoopImpl = CmpAdaptor<Impl::loop_and_tail>;
Buffer Buffer1(3 * kSize);
Buffer Buffer2(3 * kSize);
Randomize(Buffer1.span());
for (size_t size = kSize; size < 3 * kSize; ++size) {
auto span1 = Buffer1.span().subspan(0, size);
auto span2 = Buffer2.span().subspan(0, size);
- ASSERT_TRUE((CheckMemcmp<Impl::loop_and_tail>(span1, span2, size)));
+ ASSERT_TRUE((CheckMemcmp<LoopImpl>(span1, span2, size)));
}
}
}
diff --git a/libc/test/src/string/memset_test.cpp b/libc/test/src/string/memset_test.cpp
index 24c7c44a1649f..1b484ef1f06a4 100644
--- a/libc/test/src/string/memset_test.cpp
+++ b/libc/test/src/string/memset_test.cpp
@@ -6,48 +6,27 @@
//
//===----------------------------------------------------------------------===//
-#include "src/__support/CPP/span.h"
+#include "memory_utils/memory_check_utils.h"
#include "src/string/memset.h"
#include "utils/UnitTest/Test.h"
-using __llvm_libc::cpp::array;
-using __llvm_libc::cpp::span;
-using Data = array<char, 2048>;
+namespace __llvm_libc {
-static const span<const char> k_deadcode("DEADC0DE", 8);
-
-// Returns a Data object filled with a repetition of `filler`.
-Data get_data(span<const char> filler) {
- Data out;
- for (size_t i = 0; i < out.size(); ++i)
- out[i] = filler[i % filler.size()];
- return out;
+// Adapt CheckMemset signature to op implementation signatures.
+template <auto FnImpl>
+void SetAdaptor(cpp::span<char> p1, uint8_t value, size_t size) {
+ FnImpl(p1.begin(), value, size);
}
-TEST(LlvmLibcMemsetTest, Thorough) {
- const Data dirty = get_data(k_deadcode);
- for (int value = -1; value <= 1; ++value) {
- for (size_t count = 0; count < 1024; ++count) {
- for (size_t align = 0; align < 64; ++align) {
- auto buffer = dirty;
- void *const dst = &buffer[align];
- void *const ret = __llvm_libc::memset(dst, value, count);
- // Return value is `dst`.
- ASSERT_EQ(ret, dst);
- // Everything before copy is untouched.
- for (size_t i = 0; i < align; ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- // Everything in between is copied.
- for (size_t i = 0; i < count; ++i)
- ASSERT_EQ(buffer[align + i], (char)value);
- // Everything after copy is untouched.
- for (size_t i = align + count; i < dirty.size(); ++i)
- ASSERT_EQ(buffer[i], dirty[i]);
- }
- }
+TEST(LlvmLibcMemsetTest, SizeSweep) {
+ static constexpr size_t kMaxSize = 1024;
+ static constexpr auto Impl = SetAdaptor<__llvm_libc::memset>;
+ Buffer DstBuffer(kMaxSize);
+ for (size_t size = 0; size < kMaxSize; ++size) {
+ const char value = size % 10;
+ auto dst = DstBuffer.span().subspan(0, size);
+ ASSERT_TRUE((CheckMemset<Impl>(dst, value, size)));
}
}
-// FIXME: Add tests with reads and writes on the boundary of a read/write
-// protected page to check we're not reading nor writing prior/past the allowed
-// regions.
+} // namespace __llvm_libc
More information about the libc-commits
mailing list