From 3635195e0d54162ba85124fdefcac39bff703149 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Thu, 27 Oct 2022 17:01:14 +0000 Subject: [PATCH] [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 --- libc/test/src/string/bcmp_test.cpp | 42 ++-- libc/test/src/string/bzero_test.cpp | 47 ++--- libc/test/src/string/memcmp_test.cpp | 42 ++-- libc/test/src/string/memcpy_test.cpp | 54 ++--- libc/test/src/string/memmove_test.cpp | 2 +- .../src/string/memory_utils/memory_check_utils.h | 171 ++++++++++++++++ libc/test/src/string/memory_utils/op_tests.cpp | 219 +++++---------------- libc/test/src/string/memset_test.cpp | 51 ++--- 8 files changed, 310 insertions(+), 318 deletions(-) create mode 100644 libc/test/src/string/memory_utils/memory_check_utils.h diff --git a/libc/test/src/string/bcmp_test.cpp b/libc/test/src/string/bcmp_test.cpp index 8f0fe52..5f0bb0f 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 +int CmpAdaptor(cpp::span p1, cpp::span 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(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 5ae3a49..1f4be71 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; +namespace __llvm_libc { -static const span k_deadcode("DEADC0DE", 8); - -// Returns a Data object filled with a repetition of `filler`. -Data get_data(span 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 +void BzeroAdaptor(cpp::span 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(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 ab79801..9056851 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 +int CmpAdaptor(cpp::span p1, cpp::span 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(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 5ccbb4d..9963163 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; +namespace __llvm_libc { -static const span k_numbers("0123456789", 10); -static const span k_deadcode("DEADC0DE", 8); - -// Returns a Data object filled with a repetition of `filler`. -Data get_data(span 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 +void CopyAdaptor(cpp::span dst, cpp::span 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(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 451ccdb..26c1936 100644 --- a/libc/test/src/string/memmove_test.cpp +++ b/libc/test/src/string/memmove_test.cpp @@ -90,7 +90,7 @@ void Randomize(span Buffer) { current = GetRandomChar(); } -TEST(LlvmLibcMemmoveTest, Thorough) { +TEST(LlvmLibcMemmoveTest, SizeSweep) { using LargeBuffer = array; 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 0000000..19cd010 --- /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 +#include // size_t +#include // uintxx_t +#include // malloc/free + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#include +#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(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 span() { return cpp::span(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 buffer) { + for (auto ¤t : buffer) + current = GetRandomChar(); +} + +// Copy one span to another. +__attribute__((no_builtin)) static inline void +ReferenceCopy(cpp::span dst, const cpp::span 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 +bool CheckMemcpy(cpp::span dst, cpp::span 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 +bool CheckMemset(cpp::span 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 +bool CheckBcmp(cpp::span span1, cpp::span 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 differs + 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 +bool CheckMemcmp(cpp::span span1, cpp::span 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 differs + 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 715c611..fdfe9d9 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 -#include - -// User code should use macros instead of functions. -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -#include -#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 buffer) { - for (auto ¤t : buffer) - current = GetRandomChar(); -} - -// Copy one span to another. -static void Copy(cpp::span dst, const cpp::span 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 span) { - return reinterpret_cast(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(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 span() { return cpp::span(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 span) { + return reinterpret_cast(span.data()); +} + +// Adapt CheckMemcpy signature to op implementation signatures. template -bool CheckMemcpy(cpp::span dst, cpp::span src, size_t size) { - assert(dst.size() == src.size()); - assert(dst.size() == size); - Randomize(dst); +void CopyAdaptor(cpp::span dst, cpp::span 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 -static void MemcpyAdaptor(Ptr dst, CPtr src, size_t size) { - assert(size == T::SIZE); - return T::block(dst, src); +template +void CopyBlockAdaptor(cpp::span dst, cpp::span 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; Buffers SrcBuffer(kSize); Buffers DstBuffer(kSize); for (auto src : SrcBuffer.spans()) { Randomize(src); for (auto dst : DstBuffer.spans()) { - ASSERT_TRUE(CheckMemcpy>(dst, src, kSize)); + ASSERT_TRUE(CheckMemcpy(dst, src, kSize)); } } } { // Test head tail operations from kSize to 2 * kSize. + static constexpr auto HeadTailImpl = CopyAdaptor; 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(dst, src, size)); + ASSERT_TRUE(CheckMemcpy(dst, src, size)); } } { // Test loop operations from kSize to 3 * kSize. if constexpr (kSize > 1) { + static constexpr auto LoopImpl = CopyAdaptor; 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(dst, src, size)); + ASSERT_TRUE(CheckMemcpy(dst, src, size)); } } } @@ -217,48 +139,46 @@ using MemsetImplementations = testing::TypeList< generic::Memset<64, 32> // >; +// Adapt CheckMemset signature to op implementation signatures. template -bool CheckMemset(cpp::span dst, uint8_t value, size_t size) { - Randomize(dst); +void SetAdaptor(cpp::span 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 -static void MemsetAdaptor(Ptr dst, uint8_t value, size_t size) { - assert(size == T::SIZE); - return T::block(dst, value); +template +void SetBlockAdaptor(cpp::span 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; Buffers DstBuffer(kSize); for (uint8_t value : cpp::array{0, 1, 255}) { for (auto dst : DstBuffer.spans()) { - ASSERT_TRUE(CheckMemset>(dst, value, kSize)); + ASSERT_TRUE(CheckMemset(dst, value, kSize)); } } } { // Test head tail operations from kSize to 2 * kSize. + static constexpr auto HeadTailImpl = SetAdaptor; 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(dst, value, size)); + ASSERT_TRUE(CheckMemset(dst, value, size)); } } { // Test loop operations from kSize to 3 * kSize. if constexpr (kSize > 1) { + static constexpr auto LoopImpl = SetAdaptor; 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(dst, value, size))); + ASSERT_TRUE((CheckMemset(dst, value, size))); } } } @@ -295,62 +215,51 @@ using BcmpImplementations = testing::TypeList< generic::Bcmp<64> // >; +// Adapt CheckBcmp signature to op implementation signatures. template -bool CheckBcmp(cpp::span span1, cpp::span 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 differs - 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 p1, cpp::span p2, size_t size) { + return (int)FnImpl(as_byte(p1), as_byte(p2), size); } - -template -static BcmpReturnType BcmpAdaptor(CPtr p1, CPtr p2, size_t size) { - assert(size == T::SIZE); - return T::block(p1, p2); +template +int CmpBlockAdaptor(cpp::span p1, cpp::span 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; Buffers Buffer1(kSize); Buffers Buffer2(kSize); for (auto span1 : Buffer1.spans()) { Randomize(span1); for (auto span2 : Buffer2.spans()) - ASSERT_TRUE((CheckBcmp>(span1, span2, kSize))); + ASSERT_TRUE((CheckBcmp(span1, span2, kSize))); } } { // Test head tail operations from kSize to 2 * kSize. + static constexpr auto HeadTailImpl = CmpAdaptor; 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(span1, span2, size))); + ASSERT_TRUE((CheckBcmp(span1, span2, size))); } } { // Test loop operations from kSize to 3 * kSize. if constexpr (kSize > 1) { + static constexpr auto LoopImpl = CmpAdaptor; 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(span1, span2, size))); + ASSERT_TRUE((CheckBcmp(span1, span2, size))); } } } @@ -384,70 +293,40 @@ using MemcmpImplementations = testing::TypeList< generic::Memcmp<64> // >; -template -bool CheckMemcmp(cpp::span span1, cpp::span 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 differs - 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 -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; Buffers Buffer1(kSize); Buffers Buffer2(kSize); for (auto span1 : Buffer1.spans()) { Randomize(span1); for (auto span2 : Buffer2.spans()) - ASSERT_TRUE((CheckMemcmp>(span1, span2, kSize))); + ASSERT_TRUE((CheckMemcmp(span1, span2, kSize))); } } { // Test head tail operations from kSize to 2 * kSize. + static constexpr auto HeadTailImpl = CmpAdaptor; 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(span1, span2, size))); + ASSERT_TRUE((CheckMemcmp(span1, span2, size))); } } { // Test loop operations from kSize to 3 * kSize. if constexpr (kSize > 1) { + static constexpr auto LoopImpl = CmpAdaptor; 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(span1, span2, size))); + ASSERT_TRUE((CheckMemcmp(span1, span2, size))); } } } diff --git a/libc/test/src/string/memset_test.cpp b/libc/test/src/string/memset_test.cpp index 24c7c44..1b484ef 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; +namespace __llvm_libc { -static const span k_deadcode("DEADC0DE", 8); - -// Returns a Data object filled with a repetition of `filler`. -Data get_data(span 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 +void SetAdaptor(cpp::span 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(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 -- 2.7.4