| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| //===-- Elementary operations for aarch64 --------------------------------===// | ||
| // | ||
| // 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_SRC_STRING_MEMORY_UTILS_ELEMENTS_AARCH64_H | ||
| #define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_AARCH64_H | ||
|
|
||
| #include "src/__support/architectures.h" | ||
|
|
||
| #if defined(LLVM_LIBC_ARCH_AARCH64) | ||
|
|
||
| #include <src/string/memory_utils/elements.h> | ||
| #include <stddef.h> // size_t | ||
| #include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t | ||
|
|
||
| #ifdef __ARM_NEON | ||
| #include <arm_neon.h> | ||
| #endif | ||
|
|
||
| namespace __llvm_libc { | ||
| namespace aarch64_memset { | ||
| #ifdef __ARM_NEON | ||
| struct Splat8 { | ||
| static constexpr size_t SIZE = 8; | ||
| static void splat_set(char *dst, const unsigned char value) { | ||
| vst1_u8((uint8_t *)dst, vdup_n_u8(value)); | ||
| } | ||
| }; | ||
|
|
||
| struct Splat16 { | ||
| static constexpr size_t SIZE = 16; | ||
| static void splat_set(char *dst, const unsigned char value) { | ||
| vst1q_u8((uint8_t *)dst, vdupq_n_u8(value)); | ||
| } | ||
| }; | ||
|
|
||
| using _8 = Splat8; | ||
| using _16 = Splat16; | ||
| #else | ||
| using _8 = __llvm_libc::scalar::_8; | ||
| using _16 = Repeated<_8, 2>; | ||
| #endif // __ARM_NEON | ||
|
|
||
| using _1 = __llvm_libc::scalar::_1; | ||
| using _2 = __llvm_libc::scalar::_2; | ||
| using _3 = __llvm_libc::scalar::_3; | ||
| using _4 = __llvm_libc::scalar::_4; | ||
| using _32 = Chained<_16, _16>; | ||
| using _64 = Chained<_32, _32>; | ||
|
|
||
| struct Zva64 { | ||
| static constexpr size_t SIZE = 64; | ||
|
|
||
| static void splat_set(char *dst, const unsigned char) { | ||
| #if __SIZEOF_POINTER__ == 4 | ||
| asm("dc zva, %w[dst]" : : [dst] "r"(dst) : "memory"); | ||
| #else | ||
| asm("dc zva, %[dst]" : : [dst] "r"(dst) : "memory"); | ||
| #endif | ||
| } | ||
| }; | ||
|
|
||
| inline static bool hasZva() { | ||
| uint64_t zva_val; | ||
| asm("mrs %[zva_val], dczid_el0" : [zva_val] "=r"(zva_val)); | ||
| // DC ZVA is permitted if DZP, bit [4] is zero. | ||
| // BS, bits [3:0] is log2 of the block size in words. | ||
| // So the next line checks whether the instruction is permitted and block size | ||
| // is 16 words (i.e. 64 bytes). | ||
| return (zva_val & 0b11111) == 0b00100; | ||
| } | ||
|
|
||
| } // namespace aarch64_memset | ||
|
|
||
| namespace aarch64 { | ||
|
|
||
| using _1 = __llvm_libc::scalar::_1; | ||
| using _2 = __llvm_libc::scalar::_2; | ||
| using _3 = __llvm_libc::scalar::_3; | ||
| using _4 = __llvm_libc::scalar::_4; | ||
| using _8 = __llvm_libc::scalar::_8; | ||
| using _16 = __llvm_libc::scalar::_16; | ||
|
|
||
| #ifdef __ARM_NEON | ||
| struct N32 { | ||
| static constexpr size_t SIZE = 32; | ||
| static bool equals(const char *lhs, const char *rhs) { | ||
| uint8x16_t l_0 = vld1q_u8((const uint8_t *)lhs); | ||
| uint8x16_t r_0 = vld1q_u8((const uint8_t *)rhs); | ||
| uint8x16_t l_1 = vld1q_u8((const uint8_t *)(lhs + 16)); | ||
| uint8x16_t r_1 = vld1q_u8((const uint8_t *)(rhs + 16)); | ||
| uint8x16_t temp = vpmaxq_u8(veorq_u8(l_0, r_0), veorq_u8(l_1, r_1)); | ||
| uint64_t res = | ||
| vgetq_lane_u64(vreinterpretq_u64_u8(vpmaxq_u8(temp, temp)), 0); | ||
| return res == 0; | ||
| } | ||
| static int three_way_compare(const char *lhs, const char *rhs) { | ||
| uint8x16_t l_0 = vld1q_u8((const uint8_t *)lhs); | ||
| uint8x16_t r_0 = vld1q_u8((const uint8_t *)rhs); | ||
| uint8x16_t l_1 = vld1q_u8((const uint8_t *)(lhs + 16)); | ||
| uint8x16_t r_1 = vld1q_u8((const uint8_t *)(rhs + 16)); | ||
| uint8x16_t temp = vpmaxq_u8(veorq_u8(l_0, r_0), veorq_u8(l_1, r_1)); | ||
| uint64_t res = | ||
| vgetq_lane_u64(vreinterpretq_u64_u8(vpmaxq_u8(temp, temp)), 0); | ||
| if (res == 0) | ||
| return 0; | ||
| size_t index = (__builtin_ctzl(res) >> 3) << 2; | ||
| uint32_t l = *((const uint32_t *)(lhs + index)); | ||
| uint32_t r = *((const uint32_t *)(rhs + index)); | ||
| return __llvm_libc::scalar::_4::scalar_three_way_compare(l, r); | ||
| } | ||
| }; | ||
|
|
||
| using _32 = N32; | ||
| using _64 = Repeated<_32, 2>; | ||
| #else | ||
| using _32 = __llvm_libc::scalar::_32; | ||
| using _64 = __llvm_libc::scalar::_64; | ||
| #endif // __ARM_NEON | ||
|
|
||
| } // namespace aarch64 | ||
| } // namespace __llvm_libc | ||
|
|
||
| #endif // defined(LLVM_LIBC_ARCH_AARCH64) | ||
|
|
||
| #endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_AARCH64_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| //===-- Elementary operations for x86 -------------------------------------===// | ||
| // | ||
| // 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_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H | ||
| #define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H | ||
|
|
||
| #include "src/__support/CPP/bit.h" | ||
| #include "src/__support/architectures.h" | ||
|
|
||
| #if defined(LLVM_LIBC_ARCH_X86) | ||
|
|
||
| #include <stddef.h> // size_t | ||
| #include <stdint.h> // uint8_t, uint16_t, uint32_t, uint64_t | ||
|
|
||
| #ifdef __SSE2__ | ||
| #include <immintrin.h> | ||
| #endif // __SSE2__ | ||
|
|
||
| #include "src/string/memory_utils/elements.h" // __llvm_libc::scalar | ||
|
|
||
| // Fixed-size Vector Operations | ||
| // ---------------------------- | ||
|
|
||
| namespace __llvm_libc { | ||
| namespace x86 { | ||
|
|
||
| #ifdef __SSE2__ | ||
| template <typename Base> struct Vector : public Base { | ||
| static void copy(char *__restrict dst, const char *__restrict src) { | ||
| Base::store(dst, Base::load(src)); | ||
| } | ||
|
|
||
| static void move(char *dst, const char *src) { | ||
| Base::store(dst, Base::load(src)); | ||
| } | ||
|
|
||
| static bool equals(const char *a, const char *b) { | ||
| return Base::not_equal_mask(Base::load(a), Base::load(b)) == 0; | ||
| } | ||
|
|
||
| static int three_way_compare(const char *a, const char *b) { | ||
| const auto mask = Base::not_equal_mask(Base::load(a), Base::load(b)); | ||
| if (!mask) | ||
| return 0; | ||
| return char_diff(a, b, mask); | ||
| } | ||
|
|
||
| static void splat_set(char *dst, const unsigned char value) { | ||
| Base::store(dst, Base::get_splatted_value(value)); | ||
| } | ||
|
|
||
| static int char_diff(const char *a, const char *b, uint64_t mask) { | ||
| const size_t diff_index = __builtin_ctzll(mask); | ||
| const int ca = (unsigned char)a[diff_index]; | ||
| const int cb = (unsigned char)b[diff_index]; | ||
| return ca - cb; | ||
| } | ||
| }; | ||
|
|
||
| struct M128 { | ||
| static constexpr size_t SIZE = 16; | ||
| using T = char __attribute__((__vector_size__(SIZE))); | ||
| static uint16_t mask(T value) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return static_cast<uint16_t>( | ||
| _mm_movemask_epi8(cpp::bit_cast<__m128i>(value))); | ||
| } | ||
| static uint16_t not_equal_mask(T a, T b) { return mask(a != b); } | ||
| static T load(const char *ptr) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return cpp::bit_cast<T>( | ||
| _mm_loadu_si128(reinterpret_cast<__m128i_u const *>(ptr))); | ||
| } | ||
| static void store(char *ptr, T value) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return _mm_storeu_si128(reinterpret_cast<__m128i_u *>(ptr), | ||
| cpp::bit_cast<__m128i>(value)); | ||
| } | ||
| static T get_splatted_value(const char v) { | ||
| const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v}; | ||
| return splatted; | ||
| } | ||
| }; | ||
|
|
||
| using Vector128 = Vector<M128>; // 16 Bytes | ||
|
|
||
| #ifdef __AVX2__ | ||
| struct M256 { | ||
| static constexpr size_t SIZE = 32; | ||
| using T = char __attribute__((__vector_size__(SIZE))); | ||
| static uint32_t mask(T value) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return _mm256_movemask_epi8(cpp::bit_cast<__m256i>(value)); | ||
| } | ||
| static uint32_t not_equal_mask(T a, T b) { return mask(a != b); } | ||
| static T load(const char *ptr) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return cpp::bit_cast<T>( | ||
| _mm256_loadu_si256(reinterpret_cast<__m256i const *>(ptr))); | ||
| } | ||
| static void store(char *ptr, T value) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return _mm256_storeu_si256(reinterpret_cast<__m256i *>(ptr), | ||
| cpp::bit_cast<__m256i>(value)); | ||
| } | ||
| static T get_splatted_value(const char v) { | ||
| const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, | ||
| v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v}; | ||
| return splatted; | ||
| } | ||
| }; | ||
|
|
||
| using Vector256 = Vector<M256>; // 32 Bytes | ||
|
|
||
| #if defined(__AVX512F__) and defined(__AVX512BW__) | ||
| struct M512 { | ||
| static constexpr size_t SIZE = 64; | ||
| using T = char __attribute__((__vector_size__(SIZE))); | ||
| static uint64_t not_equal_mask(T a, T b) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return _mm512_cmpneq_epi8_mask(cpp::bit_cast<__m512i>(a), | ||
| cpp::bit_cast<__m512i>(b)); | ||
| } | ||
| static T load(const char *ptr) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return cpp::bit_cast<T>(_mm512_loadu_epi8(ptr)); | ||
| } | ||
| static void store(char *ptr, T value) { | ||
| // NOLINTNEXTLINE(llvmlibc-callee-namespace) | ||
| return _mm512_storeu_epi8(ptr, cpp::bit_cast<__m512i>(value)); | ||
| } | ||
| static T get_splatted_value(const char v) { | ||
| const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, | ||
| v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, | ||
| v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, | ||
| v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v}; | ||
| return splatted; | ||
| } | ||
| }; | ||
| using Vector512 = Vector<M512>; | ||
|
|
||
| #endif // defined(__AVX512F__) and defined(__AVX512BW__) | ||
| #endif // __AVX2__ | ||
| #endif // __SSE2__ | ||
|
|
||
| using _1 = __llvm_libc::scalar::_1; | ||
| using _2 = __llvm_libc::scalar::_2; | ||
| using _3 = __llvm_libc::scalar::_3; | ||
| using _4 = __llvm_libc::scalar::_4; | ||
| using _8 = __llvm_libc::scalar::_8; | ||
| #if defined(__AVX512F__) && defined(__AVX512BW__) | ||
| using _16 = __llvm_libc::x86::Vector128; | ||
| using _32 = __llvm_libc::x86::Vector256; | ||
| using _64 = __llvm_libc::x86::Vector512; | ||
| using _128 = __llvm_libc::Repeated<_64, 2>; | ||
| #elif defined(__AVX2__) | ||
| using _16 = __llvm_libc::x86::Vector128; | ||
| using _32 = __llvm_libc::x86::Vector256; | ||
| using _64 = __llvm_libc::Repeated<_32, 2>; | ||
| using _128 = __llvm_libc::Repeated<_32, 4>; | ||
| #elif defined(__SSE2__) | ||
| using _16 = __llvm_libc::x86::Vector128; | ||
| using _32 = __llvm_libc::Repeated<_16, 2>; | ||
| using _64 = __llvm_libc::Repeated<_16, 4>; | ||
| using _128 = __llvm_libc::Repeated<_16, 8>; | ||
| #else | ||
| using _16 = __llvm_libc::Repeated<_8, 2>; | ||
| using _32 = __llvm_libc::Repeated<_8, 4>; | ||
| using _64 = __llvm_libc::Repeated<_8, 8>; | ||
| using _128 = __llvm_libc::Repeated<_8, 16>; | ||
| #endif | ||
|
|
||
| struct Accelerator { | ||
| static void copy(char *dst, const char *src, size_t count) { | ||
| asm volatile("rep movsb" : "+D"(dst), "+S"(src), "+c"(count) : : "memory"); | ||
| } | ||
| }; | ||
|
|
||
| } // namespace x86 | ||
| } // namespace __llvm_libc | ||
|
|
||
| #endif // defined(LLVM_LIBC_ARCH_X86) | ||
|
|
||
| #endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| //===-- 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/__support/CPP/array.h" | ||
| #include "src/__support/CPP/span.h" | ||
| #include "src/string/memory_utils/elements.h" | ||
| #include "utils/UnitTest/Test.h" | ||
|
|
||
| namespace __llvm_libc { | ||
|
|
||
| // Registering Types | ||
| using FixedSizeTypes = testing::TypeList< | ||
| #if defined(__SSE2__) | ||
| x86::Vector128, // | ||
| #endif // __SSE2__ | ||
| #if defined(__AVX2__) | ||
| x86::Vector256, // | ||
| #endif // __AVX2__ | ||
| #if defined(__AVX512F__) and defined(__AVX512BW__) | ||
| x86::Vector512, // | ||
| #endif // defined(__AVX512F__) and defined(__AVX512BW__) | ||
| scalar::UINT8, // | ||
| scalar::UINT16, // | ||
| scalar::UINT32, // | ||
| scalar::UINT64, // | ||
| Repeated<scalar::UINT64, 2>, // | ||
| Repeated<scalar::UINT64, 4>, // | ||
| Repeated<scalar::UINT64, 8>, // | ||
| Repeated<scalar::UINT64, 16>, // | ||
| Repeated<scalar::UINT64, 32>, // | ||
| Chained<scalar::UINT16, scalar::UINT8>, // | ||
| Chained<scalar::UINT32, scalar::UINT16, scalar::UINT8>, // | ||
| builtin::_1, // | ||
| builtin::_2, // | ||
| builtin::_3, // | ||
| builtin::_4, // | ||
| builtin::_8 // | ||
| >; | ||
|
|
||
| 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(cpp::span<char> buffer) { | ||
| for (auto ¤t : buffer) | ||
| current = GetRandomChar(); | ||
| } | ||
|
|
||
| template <typename Element> using Buffer = cpp::array<char, Element::SIZE>; | ||
|
|
||
| template <typename Element> Buffer<Element> GetRandomBuffer() { | ||
| Buffer<Element> buffer; | ||
| Randomize(buffer); | ||
| return buffer; | ||
| } | ||
|
|
||
| TYPED_TEST(LlvmLibcMemoryElements, copy, FixedSizeTypes) { | ||
| Buffer<ParamType> Dst; | ||
| const auto buffer = GetRandomBuffer<ParamType>(); | ||
| copy<ParamType>(Dst.data(), buffer.data()); | ||
| for (size_t i = 0; i < ParamType::SIZE; ++i) | ||
| EXPECT_EQ(Dst[i], buffer[i]); | ||
| } | ||
|
|
||
| template <typename T> T copy(const T &Input) { | ||
| T Output; | ||
| for (size_t I = 0; I < Input.size(); ++I) | ||
| Output[I] = Input[I]; | ||
| return Output; | ||
| } | ||
|
|
||
| TYPED_TEST(LlvmLibcMemoryElements, Move, FixedSizeTypes) { | ||
| constexpr size_t SIZE = ParamType::SIZE; | ||
| using LargeBuffer = cpp::array<char, SIZE * 2>; | ||
| LargeBuffer GroundTruth; | ||
| Randomize(GroundTruth); | ||
| // Forward, we move the SIZE first bytes from offset 0 to SIZE. | ||
| for (size_t Offset = 0; Offset < SIZE; ++Offset) { | ||
| LargeBuffer Buffer = copy(GroundTruth); | ||
| move<ParamType>(&Buffer[Offset], &Buffer[0]); | ||
| for (size_t I = 0; I < SIZE; ++I) | ||
| EXPECT_EQ(Buffer[I + Offset], GroundTruth[I]); | ||
| } | ||
| // Backward, we move the SIZE last bytes from offset 0 to SIZE. | ||
| for (size_t Offset = 0; Offset < SIZE; ++Offset) { | ||
| LargeBuffer Buffer = copy(GroundTruth); | ||
| move<ParamType>(&Buffer[Offset], &Buffer[SIZE]); | ||
| for (size_t I = 0; I < SIZE; ++I) | ||
| EXPECT_EQ(Buffer[I + Offset], GroundTruth[SIZE + I]); | ||
| } | ||
| } | ||
|
|
||
| TYPED_TEST(LlvmLibcMemoryElements, Equals, FixedSizeTypes) { | ||
| const auto buffer = GetRandomBuffer<ParamType>(); | ||
| EXPECT_TRUE(equals<ParamType>(buffer.data(), buffer.data())); | ||
| } | ||
|
|
||
| TYPED_TEST(LlvmLibcMemoryElements, three_way_compare, FixedSizeTypes) { | ||
| Buffer<ParamType> initial; | ||
| for (auto &c : initial) | ||
| c = 5; | ||
|
|
||
| // Testing equality | ||
| EXPECT_EQ(three_way_compare<ParamType>(initial.data(), initial.data()), 0); | ||
|
|
||
| // Testing all mismatching positions | ||
| for (size_t i = 0; i < ParamType::SIZE; ++i) { | ||
| auto copy = initial; | ||
| ++copy[i]; // copy is now lexicographycally greated than initial | ||
| const auto *less = initial.data(); | ||
| const auto *greater = copy.data(); | ||
| EXPECT_LT(three_way_compare<ParamType>(less, greater), 0); | ||
| EXPECT_GT(three_way_compare<ParamType>(greater, less), 0); | ||
| } | ||
| } | ||
|
|
||
| TYPED_TEST(LlvmLibcMemoryElements, Splat, FixedSizeTypes) { | ||
| Buffer<ParamType> Dst; | ||
| const cpp::array<char, 3> values = {char(0x00), char(0x7F), char(0xFF)}; | ||
| for (char value : values) { | ||
| splat_set<ParamType>(Dst.data(), value); | ||
| for (size_t i = 0; i < ParamType::SIZE; ++i) | ||
| EXPECT_EQ(Dst[i], value); | ||
| } | ||
| } | ||
|
|
||
| } // namespace __llvm_libc |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| //===-- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #define LLVM_LIBC_UNITTEST_OBSERVE 1 | ||
|
|
||
| #include "src/__support/CPP/array.h" | ||
| #include "src/string/memory_utils/elements.h" | ||
| #include "utils/UnitTest/Test.h" | ||
|
|
||
| #include <stdio.h> | ||
| #include <string.h> | ||
|
|
||
| namespace __llvm_libc { | ||
|
|
||
| static constexpr const size_t kMaxBuffer = 32; | ||
|
|
||
| struct BufferAccess : cpp::array<char, kMaxBuffer + 1> { | ||
| BufferAccess() { Reset(); } | ||
| void Reset() { | ||
| for (auto &value : *this) | ||
| value = '0'; | ||
| this->operator[](kMaxBuffer) = '\0'; | ||
| } | ||
| void Touch(ptrdiff_t offset, size_t size) { | ||
| if (offset < 0) | ||
| return; | ||
| for (size_t i = 0; i < size; ++i) | ||
| ++(*this)[offset + i]; | ||
| } | ||
| operator const char *() const { return this->data(); } | ||
| }; | ||
|
|
||
| struct Buffer { | ||
| ptrdiff_t Offset(const char *ptr) const { | ||
| const bool contained = ptr >= data.begin() && ptr < data.end(); | ||
| return contained ? ptr - data.begin() : -1; | ||
| } | ||
| void Reset() { | ||
| reads.Reset(); | ||
| writes.Reset(); | ||
| } | ||
| cpp::array<char, kMaxBuffer> data; | ||
| BufferAccess __attribute__((aligned(64))) reads; | ||
| BufferAccess __attribute__((aligned(64))) writes; | ||
| }; | ||
|
|
||
| struct MemoryAccessObserver { | ||
| void ObserveRead(const char *ptr, size_t size) { | ||
| Buffer1.reads.Touch(Buffer1.Offset(ptr), size); | ||
| Buffer2.reads.Touch(Buffer2.Offset(ptr), size); | ||
| } | ||
|
|
||
| void ObserveWrite(const char *ptr, size_t size) { | ||
| Buffer1.writes.Touch(Buffer1.Offset(ptr), size); | ||
| Buffer2.writes.Touch(Buffer2.Offset(ptr), size); | ||
| } | ||
|
|
||
| void Reset() { | ||
| Buffer1.Reset(); | ||
| Buffer2.Reset(); | ||
| } | ||
|
|
||
| Buffer Buffer1; | ||
| Buffer Buffer2; | ||
| }; | ||
|
|
||
| MemoryAccessObserver Observer; | ||
|
|
||
| template <size_t Size> struct TestingElement { | ||
| static constexpr size_t SIZE = Size; | ||
|
|
||
| static void copy(char *__restrict dst, const char *__restrict src) { | ||
| Observer.ObserveRead(src, SIZE); | ||
| Observer.ObserveWrite(dst, SIZE); | ||
| } | ||
|
|
||
| static bool equals(const char *lhs, const char *rhs) { | ||
| Observer.ObserveRead(lhs, SIZE); | ||
| Observer.ObserveRead(rhs, SIZE); | ||
| return true; | ||
| } | ||
|
|
||
| static int three_way_compare(const char *lhs, const char *rhs) { | ||
| Observer.ObserveRead(lhs, SIZE); | ||
| Observer.ObserveRead(rhs, SIZE); | ||
| return 0; | ||
| } | ||
|
|
||
| static void splat_set(char *dst, const unsigned char value) { | ||
| Observer.ObserveWrite(dst, SIZE); | ||
| } | ||
| }; | ||
|
|
||
| using Types = testing::TypeList< | ||
| TestingElement<1>, // 1 Byte | ||
| TestingElement<2>, // 2 Bytes | ||
| TestingElement<4>, // 4 Bytes | ||
| Repeated<TestingElement<2>, 3>, // 6 Bytes | ||
| Chained<TestingElement<4>, TestingElement<2>, TestingElement<1>> // 7 Bytes | ||
| >; | ||
|
|
||
| struct LlvmLibcTestAccessBase : public testing::Test { | ||
|
|
||
| template <typename HigherOrder, size_t Size, size_t Offset = 0> | ||
| void checkOperations(const BufferAccess &expected) { | ||
| static const BufferAccess untouched; | ||
|
|
||
| Observer.Reset(); | ||
| HigherOrder::copy(dst_ptr() + Offset, src_ptr() + Offset, Size); | ||
| ASSERT_STREQ(src().writes, untouched); | ||
| ASSERT_STREQ(dst().reads, untouched); | ||
| ASSERT_STREQ(src().reads, expected); | ||
| ASSERT_STREQ(dst().writes, expected); | ||
| Observer.Reset(); | ||
| HigherOrder::equals(lhs_ptr() + Offset, rhs_ptr() + Offset, Size); | ||
| ASSERT_STREQ(lhs().writes, untouched); | ||
| ASSERT_STREQ(rhs().writes, untouched); | ||
| ASSERT_STREQ(lhs().reads, expected); | ||
| ASSERT_STREQ(rhs().reads, expected); | ||
| Observer.Reset(); | ||
| HigherOrder::three_way_compare(lhs_ptr() + Offset, rhs_ptr() + Offset, | ||
| Size); | ||
| ASSERT_STREQ(lhs().writes, untouched); | ||
| ASSERT_STREQ(rhs().writes, untouched); | ||
| ASSERT_STREQ(lhs().reads, expected); | ||
| ASSERT_STREQ(rhs().reads, expected); | ||
| Observer.Reset(); | ||
| HigherOrder::splat_set(dst_ptr() + Offset, 5, Size); | ||
| ASSERT_STREQ(src().reads, untouched); | ||
| ASSERT_STREQ(src().writes, untouched); | ||
| ASSERT_STREQ(dst().reads, untouched); | ||
| ASSERT_STREQ(dst().writes, expected); | ||
| } | ||
|
|
||
| void checkMaxAccess(const BufferAccess &expected, int max) { | ||
| for (size_t i = 0; i < kMaxBuffer; ++i) { | ||
| int value = (int)expected[i] - '0'; | ||
| ASSERT_GE(value, 0); | ||
| ASSERT_LE(value, max); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| const Buffer &lhs() const { return Observer.Buffer1; } | ||
| const Buffer &rhs() const { return Observer.Buffer2; } | ||
| const Buffer &src() const { return Observer.Buffer2; } | ||
| const Buffer &dst() const { return Observer.Buffer1; } | ||
| Buffer &dst() { return Observer.Buffer1; } | ||
|
|
||
| char *dst_ptr() { return dst().data.begin(); } | ||
| const char *src_ptr() { return src().data.begin(); } | ||
| const char *lhs_ptr() { return lhs().data.begin(); } | ||
| const char *rhs_ptr() { return rhs().data.begin(); } | ||
| }; | ||
|
|
||
| template <typename ParamType> | ||
| struct LlvmLibcTestAccessTail : public LlvmLibcTestAccessBase { | ||
|
|
||
| void TearDown() override { | ||
| static constexpr size_t Size = 10; | ||
|
|
||
| BufferAccess expected; | ||
| expected.Touch(Size - ParamType::SIZE, ParamType::SIZE); | ||
|
|
||
| checkMaxAccess(expected, 1); | ||
| checkOperations<Tail<ParamType>, Size>(expected); | ||
| } | ||
| }; | ||
| TYPED_TEST_F(LlvmLibcTestAccessTail, Operations, Types) {} | ||
|
|
||
| template <typename ParamType> | ||
| struct LlvmLibcTestAccessHeadTail : public LlvmLibcTestAccessBase { | ||
| void TearDown() override { | ||
| static constexpr size_t Size = 10; | ||
|
|
||
| BufferAccess expected; | ||
| expected.Touch(0, ParamType::SIZE); | ||
| expected.Touch(Size - ParamType::SIZE, ParamType::SIZE); | ||
|
|
||
| checkMaxAccess(expected, 2); | ||
| checkOperations<HeadTail<ParamType>, Size>(expected); | ||
| } | ||
| }; | ||
| TYPED_TEST_F(LlvmLibcTestAccessHeadTail, Operations, Types) {} | ||
|
|
||
| template <typename ParamType> | ||
| struct LlvmLibcTestAccessLoop : public LlvmLibcTestAccessBase { | ||
| void TearDown() override { | ||
| static constexpr size_t Size = 20; | ||
|
|
||
| BufferAccess expected; | ||
| for (size_t i = 0; i < Size - ParamType::SIZE; i += ParamType::SIZE) | ||
| expected.Touch(i, ParamType::SIZE); | ||
| expected.Touch(Size - ParamType::SIZE, ParamType::SIZE); | ||
|
|
||
| checkMaxAccess(expected, 2); | ||
| checkOperations<Loop<ParamType>, Size>(expected); | ||
| } | ||
| }; | ||
| TYPED_TEST_F(LlvmLibcTestAccessLoop, Operations, Types) {} | ||
|
|
||
| template <typename ParamType> | ||
| struct LlvmLibcTestAccessAlignedAccess : public LlvmLibcTestAccessBase { | ||
| void TearDown() override { | ||
| static constexpr size_t Size = 10; | ||
| static constexpr size_t Offset = 2; | ||
| using AlignmentT = TestingElement<4>; | ||
|
|
||
| BufferAccess expected; | ||
| expected.Touch(Offset, AlignmentT::SIZE); | ||
| expected.Touch(AlignmentT::SIZE, ParamType::SIZE); | ||
| expected.Touch(Offset + Size - ParamType::SIZE, ParamType::SIZE); | ||
|
|
||
| checkMaxAccess(expected, 3); | ||
| checkOperations<Align<AlignmentT, Arg::_1>::Then<HeadTail<ParamType>>, Size, | ||
| Offset>(expected); | ||
| checkOperations<Align<AlignmentT, Arg::_2>::Then<HeadTail<ParamType>>, Size, | ||
| Offset>(expected); | ||
| } | ||
| }; | ||
| TYPED_TEST_F(LlvmLibcTestAccessAlignedAccess, Operations, Types) {} | ||
|
|
||
| } // namespace __llvm_libc |