475 changes: 475 additions & 0 deletions libc/src/string/memory_utils/elements.h

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions libc/src/string/memory_utils/elements_x86.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//===-- 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 <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 *dst, const char *src) {
Base::Store(dst, Base::Load(src));
}

static bool Equals(const char *a, const char *b) {
return Base::NotEqualMask(Base::Load(a), Base::Load(b)) == 0;
}

static int ThreeWayCompare(const char *a, const char *b) {
const auto mask = Base::NotEqualMask(Base::Load(a), Base::Load(b));
if (!mask)
return 0;
return CharDiff(a, b, mask);
}

static void SplatSet(char *dst, const unsigned char value) {
Base::Store(dst, Base::GetSplattedValue(value));
}

static int CharDiff(const char *a, const char *b, uint64_t mask) {
const size_t diff_index = __builtin_ctzl(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 kSize = 16;
using T = char __attribute__((__vector_size__(kSize)));
static uint16_t mask(T value) { return _mm_movemask_epi8(value); }
static uint16_t NotEqualMask(T a, T b) { return mask(a != b); }
static T Load(const char *ptr) {
return _mm_loadu_si128(reinterpret_cast<__m128i_u const *>(ptr));
}
static void Store(char *ptr, T value) {
return _mm_storeu_si128(reinterpret_cast<__m128i_u *>(ptr), value);
}
static T GetSplattedValue(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 kSize = 32;
using T = char __attribute__((__vector_size__(kSize)));
static uint32_t mask(T value) { return _mm256_movemask_epi8(value); }
static uint32_t NotEqualMask(T a, T b) { return mask(a != b); }
static T Load(const char *ptr) {
return _mm256_loadu_si256(reinterpret_cast<__m256i const *>(ptr));
}
static void Store(char *ptr, T value) {
return _mm256_storeu_si256(reinterpret_cast<__m256i *>(ptr), value);
}
static T GetSplattedValue(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 kSize = 64;
using T = char __attribute__((__vector_size__(kSize)));
static uint64_t NotEqualMask(T a, T b) {
return _mm512_cmpneq_epi8_mask(a, b);
}
static T Load(const char *ptr) { return _mm512_loadu_epi8(ptr); }
static void Store(char *ptr, T value) {
return _mm512_storeu_epi8(ptr, value);
}
static T GetSplattedValue(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

} // namespace x86
} // namespace __llvm_libc

#endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H
140 changes: 0 additions & 140 deletions libc/src/string/memory_utils/memcpy_utils.h

This file was deleted.

83 changes: 15 additions & 68 deletions libc/src/string/memory_utils/memset_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,70 +6,16 @@
//
//===----------------------------------------------------------------------===//

#ifndef LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H
#define LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H
#ifndef LLVM_LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H
#define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H

#include "src/string/memory_utils/elements.h"
#include "src/string/memory_utils/utils.h"

#include <stddef.h> // size_t

namespace __llvm_libc {

// Sets `kBlockSize` bytes starting from `src` to `value`.
template <size_t kBlockSize> static void SetBlock(char *dst, unsigned value) {
// Theoretically the compiler is allowed to call memset here and end up with a
// recursive call, practically it doesn't happen, however this should be
// replaced with a __builtin_memset_inline once it's available in clang.
__builtin_memset(dst, value, kBlockSize);
}

// Sets `kBlockSize` bytes from `src + count - kBlockSize` to `value`.
// Precondition: `count >= kBlockSize`.
template <size_t kBlockSize>
static void SetLastBlock(char *dst, unsigned value, size_t count) {
SetBlock<kBlockSize>(dst + count - kBlockSize, value);
}

// Sets `kBlockSize` bytes twice with an overlap between the two.
//
// [1234567812345678123]
// [__XXXXXXXXXXXXXX___]
// [__XXXXXXXX_________]
// [________XXXXXXXX___]
//
// Precondition: `count >= kBlockSize && count <= kBlockSize`.
template <size_t kBlockSize>
static void SetBlockOverlap(char *dst, unsigned value, size_t count) {
SetBlock<kBlockSize>(dst, value);
SetLastBlock<kBlockSize>(dst, value, count);
}

// Sets `count` bytes by blocks of `kBlockSize` bytes.
// Sets at the start and end of the buffer are unaligned.
// Sets in the middle of the buffer are aligned to `kBlockSize`.
//
// e.g. with
// [12345678123456781234567812345678]
// [__XXXXXXXXXXXXXXXXXXXXXXXXXXX___]
// [__XXXXXXXX______________________]
// [________XXXXXXXX________________]
// [________________XXXXXXXX________]
// [_____________________XXXXXXXX___]
//
// Precondition: `count > 2 * kBlockSize` for efficiency.
// `count >= kBlockSize` for correctness.
template <size_t kBlockSize>
static void SetAlignedBlocks(char *dst, unsigned value, size_t count) {
SetBlock<kBlockSize>(dst, value); // Set first block

// Set aligned blocks
size_t offset = kBlockSize - offset_from_last_aligned<kBlockSize>(dst);
for (; offset + kBlockSize < count; offset += kBlockSize)
SetBlock<kBlockSize>(dst + offset, value);

SetLastBlock<kBlockSize>(dst, value, count); // Set last block
}

// A general purpose implementation assuming cheap unaligned writes for sizes:
// 1, 2, 4, 8, 16, 32 and 64 Bytes. Note that some architecture can't store 32
// or 64 Bytes at a time, the compiler will expand them as needed.
Expand Down Expand Up @@ -106,26 +52,27 @@ inline static void GeneralPurposeMemset(char *dst, unsigned char value,
if (count == 0)
return;
if (count == 1)
return SetBlock<1>(dst, value);
return SplatSet<scalar::_1>(dst, value);
if (count == 2)
return SetBlock<2>(dst, value);
return SplatSet<scalar::_2>(dst, value);
if (count == 3)
return SetBlock<3>(dst, value);
return SplatSet<scalar::_3>(dst, value);
if (count == 4)
return SetBlock<4>(dst, value);
return SplatSet<scalar::_4>(dst, value);
if (count <= 8)
return SetBlockOverlap<4>(dst, value, count);
return SplatSet<HeadTail<scalar::_4>>(dst, value, count);
if (count <= 16)
return SetBlockOverlap<8>(dst, value, count);
return SplatSet<HeadTail<scalar::_8>>(dst, value, count);
if (count <= 32)
return SetBlockOverlap<16>(dst, value, count);
return SplatSet<HeadTail<scalar::_16>>(dst, value, count);
if (count <= 64)
return SetBlockOverlap<32>(dst, value, count);
return SplatSet<HeadTail<scalar::_32>>(dst, value, count);
if (count <= 128)
return SetBlockOverlap<64>(dst, value, count);
return SetAlignedBlocks<32>(dst, value, count);
return SplatSet<HeadTail<scalar::_64>>(dst, value, count);
return SplatSet<Align<scalar::_32, Arg::Dst>::Then<Loop<scalar::_32>>>(
dst, value, count);
}

} // namespace __llvm_libc

#endif // LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H
#endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_MEMSET_UTILS_H
35 changes: 21 additions & 14 deletions libc/src/string/x86_64/memcpy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#include "src/string/memcpy.h"
#include "src/__support/common.h"
#include "src/string/memory_utils/memcpy_utils.h"
#include "src/string/memory_utils/elements.h"

namespace __llvm_libc {

Expand All @@ -29,8 +29,11 @@ constexpr size_t kRepMovsBSize =
// Whether target supports AVX instructions.
constexpr bool kHasAvx = LLVM_LIBC_IS_DEFINED(__AVX__);

// The chunk size used for the loop copy strategy.
constexpr size_t kLoopCopyBlockSize = kHasAvx ? 64 : 32;
#ifdef __AVX__
using LoopBlockSize = __llvm_libc::x86::_64;
#else
using LoopBlockSize = __llvm_libc::x86::_32;
#endif

static void CopyRepMovsb(char *__restrict dst, const char *__restrict src,
size_t count) {
Expand Down Expand Up @@ -61,33 +64,37 @@ static void CopyRepMovsb(char *__restrict dst, const char *__restrict src,
// with little change on the code side.
static void memcpy_x86(char *__restrict dst, const char *__restrict src,
size_t count) {
// Use x86 strategies (_1, _2, _3 ...)
using namespace __llvm_libc::x86;

if (kUseOnlyRepMovsb)
return CopyRepMovsb(dst, src, count);

if (count == 0)
return;
if (count == 1)
return CopyBlock<1>(dst, src);
return Copy<_1>(dst, src);
if (count == 2)
return CopyBlock<2>(dst, src);
return Copy<_2>(dst, src);
if (count == 3)
return CopyBlock<3>(dst, src);
return Copy<_3>(dst, src);
if (count == 4)
return CopyBlock<4>(dst, src);
return Copy<_4>(dst, src);
if (count < 8)
return CopyBlockOverlap<4>(dst, src, count);
return Copy<HeadTail<_4>>(dst, src, count);
if (count < 16)
return CopyBlockOverlap<8>(dst, src, count);
return Copy<HeadTail<_8>>(dst, src, count);
if (count < 32)
return CopyBlockOverlap<16>(dst, src, count);
return Copy<HeadTail<_16>>(dst, src, count);
if (count < 64)
return CopyBlockOverlap<32>(dst, src, count);
return Copy<HeadTail<_32>>(dst, src, count);
if (count < 128)
return CopyBlockOverlap<64>(dst, src, count);
return Copy<HeadTail<_64>>(dst, src, count);
if (kHasAvx && count < 256)
return CopyBlockOverlap<128>(dst, src, count);
return Copy<HeadTail<_128>>(dst, src, count);
if (count <= kRepMovsBSize)
return CopyDstAlignedBlocks<kLoopCopyBlockSize, 32>(dst, src, count);
return Copy<Align<_32, Arg::Dst>::Then<Loop<LoopBlockSize>>>(dst, src,
count);
return CopyRepMovsb(dst, src, count);
}

Expand Down
5 changes: 4 additions & 1 deletion libc/test/src/string/memory_utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ add_libc_unittest(
SUITE
libc_string_unittests
SRCS
elements_test.cpp
memory_access_test.cpp
utils_test.cpp
memcpy_utils_test.cpp
DEPENDS
libc.src.string.memory_utils.memory_utils
libc.utils.CPP.standalone_cpp
COMPILE_OPTIONS
${LIBC_COMPILE_OPTIONS_NATIVE}
)

target_compile_definitions(
Expand Down
103 changes: 103 additions & 0 deletions libc/test/src/string/memory_utils/elements_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//===-- Unittests for memory_utils ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "src/string/memory_utils/elements.h"
#include "utils/CPP/Array.h"
#include "utils/UnitTest/Test.h"

namespace __llvm_libc {

// Registering Types
using FixedSizeTypes = testing::TypeList<
#ifdef __SSE2__
x86::Vector128, //
#endif // __SSE2__
#ifdef __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;
}

template <typename Element> using Buffer = cpp::Array<char, Element::kSize>;
template <typename Element> Buffer<Element> GetRandomBuffer() {
Buffer<Element> buffer;
for (auto &current : buffer)
current = GetRandomChar();
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::kSize; ++i)
EXPECT_EQ(Dst[i], buffer[i]);
}

TYPED_TEST(LlvmLibcMemoryElements, Equals, FixedSizeTypes) {
const auto buffer = GetRandomBuffer<ParamType>();
EXPECT_TRUE(Equals<ParamType>(buffer.data(), buffer.data()));
}

TYPED_TEST(LlvmLibcMemoryElements, ThreeWayCompare, FixedSizeTypes) {
Buffer<ParamType> initial;
for (auto &c : initial)
c = 5;

// Testing equality
EXPECT_EQ(ThreeWayCompare<ParamType>(initial.data(), initial.data()), 0);

// Testing all mismatching positions
for (size_t i = 0; i < ParamType::kSize; ++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(ThreeWayCompare<ParamType>(less, greater), 0);
EXPECT_GT(ThreeWayCompare<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) {
SplatSet<ParamType>(Dst.data(), value);
for (size_t i = 0; i < ParamType::kSize; ++i)
EXPECT_EQ(Dst[i], value);
}
}

} // namespace __llvm_libc
336 changes: 0 additions & 336 deletions libc/test/src/string/memory_utils/memcpy_utils_test.cpp

This file was deleted.

231 changes: 231 additions & 0 deletions libc/test/src/string/memory_utils/memory_access_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
//===-- 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/string/memory_utils/elements.h"
#include "utils/CPP/Array.h"
#include "utils/CPP/ArrayRef.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 kSize = Size;

static void Copy(char *__restrict dst, const char *__restrict src) {
Observer.ObserveRead(src, kSize);
Observer.ObserveWrite(dst, kSize);
}

static bool Equals(const char *lhs, const char *rhs) {
Observer.ObserveRead(lhs, kSize);
Observer.ObserveRead(rhs, kSize);
return true;
}

static int ThreeWayCompare(const char *lhs, const char *rhs) {
Observer.ObserveRead(lhs, kSize);
Observer.ObserveRead(rhs, kSize);
return 0;
}

static void SplatSet(char *dst, const unsigned char value) {
Observer.ObserveWrite(dst, kSize);
}
};

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::ThreeWayCompare(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::SplatSet(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';
if (value < 0 || value > max) {
printf("expected no more than %d access, was '%s'\n", max,
(const char *)expected);
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::kSize, ParamType::kSize);

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::kSize);
expected.Touch(Size - ParamType::kSize, ParamType::kSize);

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::kSize; i += ParamType::kSize)
expected.Touch(i, ParamType::kSize);
expected.Touch(Size - ParamType::kSize, ParamType::kSize);

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::kSize);
expected.Touch(AlignmentT::kSize, ParamType::kSize);
expected.Touch(Offset + Size - ParamType::kSize, ParamType::kSize);

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