brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.8 KiB · a86cbd8 Raw
609 lines · c
1//===-- Generic implementation of memory function building blocks ---------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// This file provides generic C++ building blocks.10// Depending on the requested size, the block operation uses unsigned integral11// types, vector types or an array of the type with the maximum size.12//13// The maximum size is passed as a template argument. For instance, on x8614// platforms that only supports integral types the maximum size would be 815// (corresponding to uint64_t). On this platform if we request the size 32, this16// would be treated as a cpp::array<uint64_t, 4>.17//18// On the other hand, if the platform is x86 with support for AVX the maximum19// size is 32 and the operation can be handled with a single native operation.20//21//===----------------------------------------------------------------------===//22 23#ifndef LLVM_LIBC_SRC_STRING_MEMORY_UTILS_OP_GENERIC_H24#define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_OP_GENERIC_H25 26#include "hdr/stdint_proxy.h"27#include "src/__support/CPP/array.h"28#include "src/__support/CPP/type_traits.h"29#include "src/__support/common.h"30#include "src/__support/endian_internal.h"31#include "src/__support/macros/attributes.h" // LIBC_INLINE32#include "src/__support/macros/config.h"     // LIBC_NAMESPACE_DECL33#include "src/__support/macros/optimization.h"34#include "src/__support/macros/properties/compiler.h"35#include "src/__support/macros/properties/types.h" // LIBC_TYPES_HAS_INT6436#include "src/string/memory_utils/op_builtin.h"37#include "src/string/memory_utils/utils.h"38 39static_assert((UINTPTR_MAX == 4294967295U) ||40                  (UINTPTR_MAX == 18446744073709551615UL),41              "We currently only support 32- or 64-bit platforms");42 43#ifdef LIBC_COMPILER_IS_MSVC44#ifdef LIBC_TARGET_ARCH_IS_X8645namespace LIBC_NAMESPACE_DECL {46using generic_v128 = __m128i;47using generic_v256 = __m256i;48using generic_v512 = __m512i;49} // namespace LIBC_NAMESPACE_DECL50#else51// Special handling when target does not have real vector types.52// We can potentially use uint8x16_t etc. However, MSVC does not provide53// subscript operation.54namespace LIBC_NAMESPACE_DECL {55struct alignas(16) generic_v128 : public cpp::array<uint8_t, 16> {};56struct alignas(32) generic_v256 : public cpp::array<uint8_t, 32> {};57struct alignas(64) generic_v512 : public cpp::array<uint8_t, 64> {};58} // namespace LIBC_NAMESPACE_DECL59#endif60 61#else62namespace LIBC_NAMESPACE_DECL {63// Compiler types using the vector attributes.64using generic_v128 = uint8_t __attribute__((__vector_size__(16)));65using generic_v256 = uint8_t __attribute__((__vector_size__(32)));66using generic_v512 = uint8_t __attribute__((__vector_size__(64)));67} // namespace LIBC_NAMESPACE_DECL68#endif // LIBC_COMPILER_IS_MSVC69 70namespace LIBC_NAMESPACE_DECL {71namespace generic {72 73// We accept three types of values as elements for generic operations:74// - scalar : unsigned integral types,75// - vector : compiler types using the vector attributes or platform builtins,76// - array  : a cpp::array<T, N> where T is itself either a scalar or a vector.77// The following traits help discriminate between these cases.78 79template <typename T> struct is_scalar : cpp::false_type {};80template <> struct is_scalar<uint8_t> : cpp::true_type {};81template <> struct is_scalar<uint16_t> : cpp::true_type {};82template <> struct is_scalar<uint32_t> : cpp::true_type {};83#ifdef LIBC_TYPES_HAS_INT6484template <> struct is_scalar<uint64_t> : cpp::true_type {};85#endif // LIBC_TYPES_HAS_INT6486// Meant to match std::numeric_limits interface.87// NOLINTNEXTLINE(readability-identifier-naming)88template <typename T> constexpr bool is_scalar_v = is_scalar<T>::value;89 90template <typename T> struct is_vector : cpp::false_type {};91template <> struct is_vector<generic_v128> : cpp::true_type {};92template <> struct is_vector<generic_v256> : cpp::true_type {};93template <> struct is_vector<generic_v512> : cpp::true_type {};94// Meant to match std::numeric_limits interface.95// NOLINTNEXTLINE(readability-identifier-naming)96template <typename T> constexpr bool is_vector_v = is_vector<T>::value;97 98template <class T> struct is_array : cpp::false_type {};99template <class T, size_t N> struct is_array<cpp::array<T, N>> {100  // Meant to match std::numeric_limits interface.101  // NOLINTNEXTLINE(readability-identifier-naming)102  static constexpr bool value = is_scalar_v<T> || is_vector_v<T>;103};104// Meant to match std::numeric_limits interface.105// NOLINTNEXTLINE(readability-identifier-naming)106template <typename T> constexpr bool is_array_v = is_array<T>::value;107 108// Meant to match std::numeric_limits interface.109// NOLINTBEGIN(readability-identifier-naming)110template <typename T>111constexpr bool is_element_type_v =112    is_scalar_v<T> || is_vector_v<T> || is_array_v<T>;113// NOLINTEND(readability-identifier-naming)114 115// Helper struct to retrieve the number of elements of an array.116template <class T> struct array_size {};117template <class T, size_t N>118struct array_size<cpp::array<T, N>> : cpp::integral_constant<size_t, N> {};119// Meant to match std::numeric_limits interface.120// NOLINTNEXTLINE(readability-identifier-naming)121template <typename T> constexpr size_t array_size_v = array_size<T>::value;122 123// Generic operations for the above type categories.124 125template <typename T> T load(CPtr src) {126  static_assert(is_element_type_v<T>);127  if constexpr (is_scalar_v<T> || is_vector_v<T>) {128    return ::LIBC_NAMESPACE::load<T>(src);129  } else if constexpr (is_array_v<T>) {130    using value_type = typename T::value_type;131    T value;132    for (size_t i = 0; i < array_size_v<T>; ++i)133      value[i] = load<value_type>(src + (i * sizeof(value_type)));134    return value;135  }136}137 138template <typename T> void store(Ptr dst, T value) {139  static_assert(is_element_type_v<T>);140  if constexpr (is_scalar_v<T> || is_vector_v<T>) {141    ::LIBC_NAMESPACE::store<T>(dst, value);142  } else if constexpr (is_array_v<T>) {143    using value_type = typename T::value_type;144    for (size_t i = 0; i < array_size_v<T>; ++i)145      store<value_type>(dst + (i * sizeof(value_type)), value[i]);146  }147}148 149template <typename T> T splat(uint8_t value) {150  static_assert(is_scalar_v<T> || is_vector_v<T>);151  if constexpr (is_scalar_v<T>)152    return T(~0) / T(0xFF) * T(value);153  else if constexpr (is_vector_v<T>) {154    T out;155    // This for loop is optimized out for vector types.156    for (size_t i = 0; i < sizeof(T); ++i)157      out[i] = value;158    return out;159  }160}161 162///////////////////////////////////////////////////////////////////////////////163// Memset164///////////////////////////////////////////////////////////////////////////////165 166template <typename T> struct Memset {167  static_assert(is_element_type_v<T>);168  static constexpr size_t SIZE = sizeof(T);169 170  LIBC_INLINE static void block(Ptr dst, uint8_t value) {171    if constexpr (is_scalar_v<T> || is_vector_v<T>) {172      // Avoid ambiguous call due to ADL173      generic::store<T>(dst, splat<T>(value));174    } else if constexpr (is_array_v<T>) {175      using value_type = typename T::value_type;176      const auto Splat = splat<value_type>(value);177      for (size_t i = 0; i < array_size_v<T>; ++i)178        store<value_type>(dst + (i * sizeof(value_type)), Splat);179    }180  }181 182  LIBC_INLINE static void tail(Ptr dst, uint8_t value, size_t count) {183    block(dst + count - SIZE, value);184  }185 186  LIBC_INLINE static void head_tail(Ptr dst, uint8_t value, size_t count) {187    block(dst, value);188    tail(dst, value, count);189  }190 191  LIBC_INLINE static void loop_and_tail_offset(Ptr dst, uint8_t value,192                                               size_t count, size_t offset) {193    static_assert(SIZE > 1, "a loop of size 1 does not need tail");194    do {195      block(dst + offset, value);196      offset += SIZE;197    } while (offset < count - SIZE);198    tail(dst, value, count);199  }200 201  LIBC_INLINE static void loop_and_tail(Ptr dst, uint8_t value, size_t count) {202    return loop_and_tail_offset(dst, value, count, 0);203  }204};205 206template <typename T, typename... TS> struct MemsetSequence {207  static constexpr size_t SIZE = (sizeof(T) + ... + sizeof(TS));208  LIBC_INLINE static void block(Ptr dst, uint8_t value) {209    Memset<T>::block(dst, value);210    if constexpr (sizeof...(TS) > 0)211      return MemsetSequence<TS...>::block(dst + sizeof(T), value);212  }213};214 215///////////////////////////////////////////////////////////////////////////////216// Memmove217///////////////////////////////////////////////////////////////////////////////218 219template <typename T> struct Memmove {220  static_assert(is_element_type_v<T>);221  static constexpr size_t SIZE = sizeof(T);222 223  LIBC_INLINE static void block(Ptr dst, CPtr src) {224    store<T>(dst, load<T>(src));225  }226 227  LIBC_INLINE static void head_tail(Ptr dst, CPtr src, size_t count) {228    const size_t offset = count - SIZE;229    // The load and store operations can be performed in any order as long as230    // they are not interleaved. More investigations are needed to determine231    // the best order.232    const auto head = load<T>(src);233    const auto tail = load<T>(src + offset);234    store<T>(dst, head);235    store<T>(dst + offset, tail);236  }237 238  // Align forward suitable when dst < src. The alignment is performed with239  // an HeadTail operation of count ∈ [Alignment, 2 x Alignment].240  //241  // e.g. Moving two bytes forward, we make sure src is aligned.242  // [  |       |       |       |      ]243  // [____XXXXXXXXXXXXXXXXXXXXXXXXXXXX_]244  // [____LLLLLLLL_____________________]245  // [___________LLLLLLLA______________]246  // [_SSSSSSSS________________________]247  // [________SSSSSSSS_________________]248  //249  // e.g. Moving two bytes forward, we make sure dst is aligned.250  // [  |       |       |       |      ]251  // [____XXXXXXXXXXXXXXXXXXXXXXXXXXXX_]252  // [____LLLLLLLL_____________________]253  // [______LLLLLLLL___________________]254  // [_SSSSSSSS________________________]255  // [___SSSSSSSA______________________]256  template <Arg AlignOn>257  LIBC_INLINE static void align_forward(Ptr &dst, CPtr &src, size_t &count) {258    Ptr prev_dst = dst;259    CPtr prev_src = src;260    size_t prev_count = count;261    align_to_next_boundary<SIZE, AlignOn>(dst, src, count);262    adjust(SIZE, dst, src, count);263    head_tail(prev_dst, prev_src, prev_count - count);264  }265 266  // Align backward suitable when dst > src. The alignment is performed with267  // an HeadTail operation of count ∈ [Alignment, 2 x Alignment].268  //269  // e.g. Moving two bytes backward, we make sure src is aligned.270  // [  |       |       |       |      ]271  // [____XXXXXXXXXXXXXXXXXXXXXXXX_____]272  // [ _________________ALLLLLLL_______]273  // [ ___________________LLLLLLLL_____]274  // [____________________SSSSSSSS_____]275  // [______________________SSSSSSSS___]276  //277  // e.g. Moving two bytes backward, we make sure dst is aligned.278  // [  |       |       |       |      ]279  // [____XXXXXXXXXXXXXXXXXXXXXXXX_____]280  // [ _______________LLLLLLLL_________]281  // [ ___________________LLLLLLLL_____]282  // [__________________ASSSSSSS_______]283  // [______________________SSSSSSSS___]284  template <Arg AlignOn>285  LIBC_INLINE static void align_backward(Ptr &dst, CPtr &src, size_t &count) {286    Ptr headtail_dst = dst + count;287    CPtr headtail_src = src + count;288    size_t headtail_size = 0;289    align_to_next_boundary<SIZE, AlignOn>(headtail_dst, headtail_src,290                                          headtail_size);291    adjust(-2 * SIZE, headtail_dst, headtail_src, headtail_size);292    head_tail(headtail_dst, headtail_src, headtail_size);293    count -= headtail_size;294  }295 296  // Move forward suitable when dst < src. We load the tail bytes before297  // handling the loop.298  //299  // e.g. Moving two bytes300  // [   |       |       |       |       |]301  // [___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX___]302  // [_________________________LLLLLLLL___]303  // [___LLLLLLLL_________________________]304  // [_SSSSSSSS___________________________]305  // [___________LLLLLLLL_________________]306  // [_________SSSSSSSS___________________]307  // [___________________LLLLLLLL_________]308  // [_________________SSSSSSSS___________]309  // [_______________________SSSSSSSS_____]310  LIBC_INLINE static void loop_and_tail_forward(Ptr dst, CPtr src,311                                                size_t count) {312    static_assert(SIZE > 1, "a loop of size 1 does not need tail");313    const size_t tail_offset = count - SIZE;314    const auto tail_value = load<T>(src + tail_offset);315    size_t offset = 0;316    LIBC_LOOP_NOUNROLL317    do {318      block(dst + offset, src + offset);319      offset += SIZE;320    } while (offset < count - SIZE);321    store<T>(dst + tail_offset, tail_value);322  }323 324  // Move backward suitable when dst > src. We load the head bytes before325  // handling the loop.326  //327  // e.g. Moving two bytes328  // [   |       |       |       |       |]329  // [___XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX___]330  // [___LLLLLLLL_________________________]331  // [_________________________LLLLLLLL___]332  // [___________________________SSSSSSSS_]333  // [_________________LLLLLLLL___________]334  // [___________________SSSSSSSS_________]335  // [_________LLLLLLLL___________________]336  // [___________SSSSSSSS_________________]337  // [_____SSSSSSSS_______________________]338  LIBC_INLINE static void loop_and_tail_backward(Ptr dst, CPtr src,339                                                 size_t count) {340    static_assert(SIZE > 1, "a loop of size 1 does not need tail");341    const auto head_value = load<T>(src);342    ptrdiff_t offset = count - SIZE;343    LIBC_LOOP_NOUNROLL344    do {345      block(dst + offset, src + offset);346      offset -= SIZE;347    } while (offset >= 0);348    store<T>(dst, head_value);349  }350};351 352///////////////////////////////////////////////////////////////////////////////353// Low level operations for Bcmp and Memcmp that operate on memory locations.354///////////////////////////////////////////////////////////////////////////////355 356// Same as load above but with an offset to the pointer.357// Making the offset explicit hints the compiler to use relevant addressing mode358// consistently.359template <typename T> LIBC_INLINE T load(CPtr ptr, size_t offset) {360  return ::LIBC_NAMESPACE::load<T>(ptr + offset);361}362 363// Same as above but also makes sure the loaded value is in big endian format.364// This is useful when implementing lexicograhic comparisons as big endian365// scalar comparison directly maps to lexicographic byte comparisons.366template <typename T> LIBC_INLINE T load_be(CPtr ptr, size_t offset) {367  return Endian::to_big_endian(load<T>(ptr, offset));368}369 370// Equality: returns true iff values at locations (p1 + offset) and (p2 +371// offset) compare equal.372template <typename T> LIBC_INLINE bool eq(CPtr p1, CPtr p2, size_t offset);373 374// Not equals: returns non-zero iff values at locations (p1 + offset) and (p2 +375// offset) differ.376template <typename T> LIBC_INLINE uint32_t neq(CPtr p1, CPtr p2, size_t offset);377 378// Lexicographic comparison:379// - returns 0 iff values at locations (p1 + offset) and (p2 + offset) compare380//   equal.381// - returns a negative value if value at location (p1 + offset) is382//   lexicographically less than value at (p2 + offset).383// - returns a positive value if value at location (p1 + offset) is384//   lexicographically greater than value at (p2 + offset).385template <typename T>386LIBC_INLINE MemcmpReturnType cmp(CPtr p1, CPtr p2, size_t offset);387 388// Lexicographic comparison of non-equal values:389// - returns a negative value if value at location (p1 + offset) is390//   lexicographically less than value at (p2 + offset).391// - returns a positive value if value at location (p1 + offset) is392//   lexicographically greater than value at (p2 + offset).393template <typename T>394LIBC_INLINE MemcmpReturnType cmp_neq(CPtr p1, CPtr p2, size_t offset);395 396///////////////////////////////////////////////////////////////////////////////397// Memcmp implementation398//399// When building memcmp, not all types are considered equals.400//401// For instance, the lexicographic comparison of two uint8_t can be implemented402// as a simple subtraction, but for wider operations the logic can be much more403// involving, especially on little endian platforms.404//405// For such wider types it is a good strategy to test for equality first and406// only do the expensive lexicographic comparison if necessary.407//408// Decomposing the algorithm like this for wider types allows us to have409// efficient implementation of higher order functions like 'head_tail' or410// 'loop_and_tail'.411///////////////////////////////////////////////////////////////////////////////412 413// Type traits to decide whether we can use 'cmp' directly or if we need to414// split the computation.415template <typename T> struct cmp_is_expensive;416 417template <typename T> struct Memcmp {418  static_assert(is_element_type_v<T>);419  static constexpr size_t SIZE = sizeof(T);420 421private:422  LIBC_INLINE static MemcmpReturnType block_offset(CPtr p1, CPtr p2,423                                                   size_t offset) {424    if constexpr (cmp_is_expensive<T>::value) {425      if (!eq<T>(p1, p2, offset))426        return cmp_neq<T>(p1, p2, offset);427      return MemcmpReturnType::zero();428    } else {429      return cmp<T>(p1, p2, offset);430    }431  }432 433public:434  LIBC_INLINE static MemcmpReturnType block(CPtr p1, CPtr p2) {435    return block_offset(p1, p2, 0);436  }437 438  LIBC_INLINE static MemcmpReturnType tail(CPtr p1, CPtr p2, size_t count) {439    return block_offset(p1, p2, count - SIZE);440  }441 442  LIBC_INLINE static MemcmpReturnType head_tail(CPtr p1, CPtr p2,443                                                size_t count) {444    if constexpr (cmp_is_expensive<T>::value) {445      if (!eq<T>(p1, p2, 0))446        return cmp_neq<T>(p1, p2, 0);447    } else {448      if (const auto value = cmp<T>(p1, p2, 0))449        return value;450    }451    return tail(p1, p2, count);452  }453 454  LIBC_INLINE static MemcmpReturnType loop_and_tail(CPtr p1, CPtr p2,455                                                    size_t count) {456    return loop_and_tail_offset(p1, p2, count, 0);457  }458 459  LIBC_INLINE static MemcmpReturnType460  loop_and_tail_offset(CPtr p1, CPtr p2, size_t count, size_t offset) {461    if constexpr (SIZE > 1) {462      const size_t limit = count - SIZE;463      LIBC_LOOP_NOUNROLL464      for (; offset < limit; offset += SIZE) {465        if constexpr (cmp_is_expensive<T>::value) {466          if (!eq<T>(p1, p2, offset))467            return cmp_neq<T>(p1, p2, offset);468        } else {469          if (const auto value = cmp<T>(p1, p2, offset))470            return value;471        }472      }473      return block_offset(p1, p2, limit); // tail474    } else {475      // No need for a tail operation when SIZE == 1.476      LIBC_LOOP_NOUNROLL477      for (; offset < count; offset += SIZE)478        if (auto value = cmp<T>(p1, p2, offset))479          return value;480      return MemcmpReturnType::zero();481    }482  }483 484  LIBC_INLINE static MemcmpReturnType485  loop_and_tail_align_above(size_t threshold, CPtr p1, CPtr p2, size_t count) {486    const AlignHelper<sizeof(T)> helper(p1);487    if (LIBC_UNLIKELY(count >= threshold) && helper.not_aligned()) {488      if (auto value = block(p1, p2))489        return value;490      adjust(helper.offset, p1, p2, count);491    }492    return loop_and_tail(p1, p2, count);493  }494};495 496template <typename T, typename... TS> struct MemcmpSequence {497  static constexpr size_t SIZE = (sizeof(T) + ... + sizeof(TS));498  LIBC_INLINE static MemcmpReturnType block(CPtr p1, CPtr p2) {499    // TODO: test suggestion in500    // https://reviews.llvm.org/D148717?id=515724#inline-1446890501    // once we have a proper way to check memory operation latency.502    if constexpr (cmp_is_expensive<T>::value) {503      if (!eq<T>(p1, p2, 0))504        return cmp_neq<T>(p1, p2, 0);505    } else {506      if (auto value = cmp<T>(p1, p2, 0))507        return value;508    }509    if constexpr (sizeof...(TS) > 0)510      return MemcmpSequence<TS...>::block(p1 + sizeof(T), p2 + sizeof(T));511    else512      return MemcmpReturnType::zero();513  }514};515 516///////////////////////////////////////////////////////////////////////////////517// Bcmp518///////////////////////////////////////////////////////////////////////////////519template <typename T> struct Bcmp {520  static_assert(is_element_type_v<T>);521  static constexpr size_t SIZE = sizeof(T);522 523  LIBC_INLINE static BcmpReturnType block(CPtr p1, CPtr p2) {524    return neq<T>(p1, p2, 0);525  }526 527  LIBC_INLINE static BcmpReturnType tail(CPtr p1, CPtr p2, size_t count) {528    const size_t tail_offset = count - SIZE;529    return neq<T>(p1, p2, tail_offset);530  }531 532  LIBC_INLINE static BcmpReturnType head_tail(CPtr p1, CPtr p2, size_t count) {533    if (const auto value = neq<T>(p1, p2, 0))534      return value;535    return tail(p1, p2, count);536  }537 538  LIBC_INLINE static BcmpReturnType loop_and_tail(CPtr p1, CPtr p2,539                                                  size_t count) {540    return loop_and_tail_offset(p1, p2, count, 0);541  }542 543  LIBC_INLINE static BcmpReturnType544  loop_and_tail_offset(CPtr p1, CPtr p2, size_t count, size_t offset) {545    if constexpr (SIZE > 1) {546      const size_t limit = count - SIZE;547      LIBC_LOOP_NOUNROLL548      for (; offset < limit; offset += SIZE)549        if (const auto value = neq<T>(p1, p2, offset))550          return value;551      return tail(p1, p2, count);552    } else {553      // No need for a tail operation when SIZE == 1.554      LIBC_LOOP_NOUNROLL555      for (; offset < count; offset += SIZE)556        if (const auto value = neq<T>(p1, p2, offset))557          return value;558      return BcmpReturnType::zero();559    }560  }561 562  LIBC_INLINE static BcmpReturnType563  loop_and_tail_align_above(size_t threshold, CPtr p1, CPtr p2, size_t count) {564    static_assert(SIZE > 1,565                  "No need to align when processing one byte at a time");566    const AlignHelper<sizeof(T)> helper(p1);567    if (LIBC_UNLIKELY(count >= threshold) && helper.not_aligned()) {568      if (auto value = block(p1, p2))569        return value;570      adjust(helper.offset, p1, p2, count);571    }572    return loop_and_tail(p1, p2, count);573  }574};575 576template <typename T, typename... TS> struct BcmpSequence {577  static constexpr size_t SIZE = (sizeof(T) + ... + sizeof(TS));578  LIBC_INLINE static BcmpReturnType block(CPtr p1, CPtr p2) {579    if (auto value = neq<T>(p1, p2, 0))580      return value;581    if constexpr (sizeof...(TS) > 0)582      return BcmpSequence<TS...>::block(p1 + sizeof(T), p2 + sizeof(T));583    else584      return BcmpReturnType::zero();585  }586};587 588///////////////////////////////////////////////////////////////////////////////589// Specializations for uint8_t590template <> struct cmp_is_expensive<uint8_t> : public cpp::false_type {};591template <> LIBC_INLINE bool eq<uint8_t>(CPtr p1, CPtr p2, size_t offset) {592  return load<uint8_t>(p1, offset) == load<uint8_t>(p2, offset);593}594template <> LIBC_INLINE uint32_t neq<uint8_t>(CPtr p1, CPtr p2, size_t offset) {595  return load<uint8_t>(p1, offset) ^ load<uint8_t>(p2, offset);596}597template <>598LIBC_INLINE MemcmpReturnType cmp<uint8_t>(CPtr p1, CPtr p2, size_t offset) {599  return static_cast<int32_t>(load<uint8_t>(p1, offset)) -600         static_cast<int32_t>(load<uint8_t>(p2, offset));601}602template <>603LIBC_INLINE MemcmpReturnType cmp_neq<uint8_t>(CPtr p1, CPtr p2, size_t offset);604 605} // namespace generic606} // namespace LIBC_NAMESPACE_DECL607 608#endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_OP_GENERIC_H609