//===-- Memory utils --------------------------------------------*- C++ -*-===// // // 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_MEMORY_UTILS_UTILS_H #define LLVM_LIBC_SRC_MEMORY_UTILS_UTILS_H #include "src/__support/CPP/bit.h" #include "src/__support/CPP/cstddef.h" #include "src/__support/CPP/type_traits.h" #include "src/__support/endian.h" #include "src/__support/macros/attributes.h" // LIBC_INLINE #include "src/__support/macros/config.h" // LIBC_HAS_BUILTIN #include // size_t #include // intptr_t / uintptr_t namespace __llvm_libc { // Allows compile time error reporting in `if constexpr` branches. template static void deferred_static_assert(const char *msg) { static_assert(flag, "compilation error"); (void)msg; } // Return whether `value` is zero or a power of two. static constexpr bool is_power2_or_zero(size_t value) { return (value & (value - 1U)) == 0; } // Return whether `value` is a power of two. static constexpr bool is_power2(size_t value) { return value && is_power2_or_zero(value); } // Compile time version of log2 that handles 0. static constexpr size_t log2(size_t value) { return (value == 0 || value == 1) ? 0 : 1 + log2(value / 2); } // Returns the first power of two preceding value or value if it is already a // power of two (or 0 when value is 0). static constexpr size_t le_power2(size_t value) { return value == 0 ? value : 1ULL << log2(value); } // Returns the first power of two following value or value if it is already a // power of two (or 0 when value is 0). static constexpr size_t ge_power2(size_t value) { return is_power2_or_zero(value) ? value : 1ULL << (log2(value) + 1); } // Returns the number of bytes to substract from ptr to get to the previous // multiple of alignment. If ptr is already aligned returns 0. template uintptr_t distance_to_align_down(const void *ptr) { static_assert(is_power2(alignment), "alignment must be a power of 2"); return reinterpret_cast(ptr) & (alignment - 1U); } // Returns the number of bytes to add to ptr to get to the next multiple of // alignment. If ptr is already aligned returns 0. template uintptr_t distance_to_align_up(const void *ptr) { static_assert(is_power2(alignment), "alignment must be a power of 2"); // The logic is not straightforward and involves unsigned modulo arithmetic // but the generated code is as fast as it can be. return -reinterpret_cast(ptr) & (alignment - 1U); } // Returns the number of bytes to add to ptr to get to the next multiple of // alignment. If ptr is already aligned returns alignment. template uintptr_t distance_to_next_aligned(const void *ptr) { return alignment - distance_to_align_down(ptr); } // Returns the same pointer but notifies the compiler that it is aligned. template static T *assume_aligned(T *ptr) { return reinterpret_cast(__builtin_assume_aligned(ptr, alignment)); } #if LIBC_HAS_BUILTIN(__builtin_memcpy_inline) #define LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE #endif #if LIBC_HAS_BUILTIN(__builtin_memset_inline) #define LLVM_LIBC_HAS_BUILTIN_MEMSET_INLINE #endif // Performs a constant count copy. template LIBC_INLINE void memcpy_inline(void *__restrict dst, const void *__restrict src) { #ifdef LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE __builtin_memcpy_inline(dst, src, Size); #else // In memory functions `memcpy_inline` is instantiated several times with // different value of the Size parameter. This doesn't play well with GCC's // Value Range Analysis that wrongly detects out of bounds accesses. We disable // the 'array-bounds' warning for the purpose of this function. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warray-bounds" for (size_t i = 0; i < Size; ++i) static_cast(dst)[i] = static_cast(src)[i]; #pragma GCC diagnostic pop #endif } using Ptr = cpp::byte *; // Pointer to raw data. using CPtr = const cpp::byte *; // Const pointer to raw data. // This type makes sure that we don't accidentally promote an integral type to // another one. It is only constructible from the exact T type. template struct StrictIntegralType { static_assert(cpp::is_integral_v); // Can only be constructed from a T. template , bool> = 0> StrictIntegralType(U value) : value(value) {} // Allows using the type in an if statement. explicit operator bool() const { return value; } // If type is unsigned (bcmp) we allow bitwise OR operations. StrictIntegralType operator|(const StrictIntegralType &Rhs) const { static_assert(!cpp::is_signed_v); return value | Rhs.value; } // For interation with the C API we allow explicit conversion back to the // `int` type. explicit operator int() const { // bit_cast makes sure that T and int have the same size. return cpp::bit_cast(value); } // Helper to get the zero value. LIBC_INLINE static constexpr StrictIntegralType ZERO() { return {T(0)}; } LIBC_INLINE static constexpr StrictIntegralType NONZERO() { return {T(1)}; } private: T value; }; using MemcmpReturnType = StrictIntegralType; using BcmpReturnType = StrictIntegralType; // Loads bytes from memory (possibly unaligned) and materializes them as // type. template LIBC_INLINE T load(CPtr ptr) { T Out; memcpy_inline(&Out, ptr); return Out; } // Stores a value of type T in memory (possibly unaligned). template LIBC_INLINE void store(Ptr ptr, T value) { memcpy_inline(ptr, &value); } // On architectures that do not allow for unaligned access we perform several // aligned accesses and recombine them through shifts and logicals operations. // For instance, if we know that the pointer is 2-byte aligned we can decompose // a 64-bit operation into four 16-bit operations. // Loads a 'ValueType' by decomposing it into several loads that are assumed to // be aligned. // e.g. load_aligned(ptr); template ValueType load_aligned(CPtr src) { static_assert(sizeof(ValueType) >= (sizeof(T) + ... + sizeof(TS))); const ValueType value = load(assume_aligned(src)); if constexpr (sizeof...(TS) > 0) { constexpr size_t shift = sizeof(T) * 8; const ValueType next = load_aligned(src + sizeof(T)); if constexpr (Endian::IS_LITTLE) return value | (next << shift); else if constexpr (Endian::IS_BIG) return (value << shift) | next; else deferred_static_assert("Invalid endianness"); } else { return value; } } // Alias for loading a 'uint32_t'. template auto load32_aligned(CPtr src, size_t offset) { static_assert((sizeof(T) + ... + sizeof(TS)) == sizeof(uint32_t)); return load_aligned(src + offset); } // Alias for loading a 'uint64_t'. template auto load64_aligned(CPtr src, size_t offset) { static_assert((sizeof(T) + ... + sizeof(TS)) == sizeof(uint64_t)); return load_aligned(src + offset); } // Stores a 'ValueType' by decomposing it into several stores that are assumed // to be aligned. // e.g. store_aligned(value, ptr); template void store_aligned(ValueType value, Ptr dst) { static_assert(sizeof(ValueType) >= (sizeof(T) + ... + sizeof(TS))); constexpr size_t shift = sizeof(T) * 8; if constexpr (Endian::IS_LITTLE) { store(assume_aligned(dst), value & ~T(0)); if constexpr (sizeof...(TS) > 0) store_aligned(value >> shift, dst + sizeof(T)); } else if constexpr (Endian::IS_BIG) { constexpr size_t OFFSET = (0 + ... + sizeof(TS)); store(assume_aligned(dst + OFFSET), value & ~T(0)); if constexpr (sizeof...(TS) > 0) store_aligned(value >> shift, dst); } else { deferred_static_assert("Invalid endianness"); } } // Alias for storing a 'uint32_t'. template void store32_aligned(uint32_t value, Ptr dst, size_t offset) { static_assert((sizeof(T) + ... + sizeof(TS)) == sizeof(uint32_t)); store_aligned(value, dst + offset); } // Alias for storing a 'uint64_t'. template void store64_aligned(uint64_t value, Ptr dst, size_t offset) { static_assert((sizeof(T) + ... + sizeof(TS)) == sizeof(uint64_t)); store_aligned(value, dst + offset); } // Advances the pointers p1 and p2 by offset bytes and decrease count by the // same amount. template LIBC_INLINE void adjust(ptrdiff_t offset, T1 *__restrict &p1, T2 *__restrict &p2, size_t &count) { p1 += offset; p2 += offset; count -= offset; } // Advances p1 and p2 so p1 gets aligned to the next SIZE bytes boundary // and decrease count by the same amount. // We make sure the compiler knows about the adjusted pointer alignment. template void align_p1_to_next_boundary(T1 *__restrict &p1, T2 *__restrict &p2, size_t &count) { adjust(distance_to_next_aligned(p1), p1, p2, count); p1 = assume_aligned(p1); } // Same as align_p1_to_next_boundary above but with a single pointer instead. template void align_to_next_boundary(T1 *&p1, size_t &count) { CPtr dummy; align_p1_to_next_boundary(p1, dummy, count); } // An enum class that discriminates between the first and second pointer. enum class Arg { P1, P2, Dst = P1, Src = P2 }; // Same as align_p1_to_next_boundary but allows for aligning p2 instead of p1. // Precondition: &p1 != &p2 template void align_to_next_boundary(T1 *__restrict &p1, T2 *__restrict &p2, size_t &count) { if constexpr (AlignOn == Arg::P1) align_p1_to_next_boundary(p1, p2, count); else if constexpr (AlignOn == Arg::P2) align_p1_to_next_boundary(p2, p1, count); // swapping p1 and p2. else deferred_static_assert("AlignOn must be either Arg::P1 or Arg::P2"); } } // namespace __llvm_libc #endif // LLVM_LIBC_SRC_MEMORY_UTILS_UTILS_H