diff --git a/libc/src/__support/CPP/TypeTraits.h b/libc/src/__support/CPP/TypeTraits.h --- a/libc/src/__support/CPP/TypeTraits.h +++ b/libc/src/__support/CPP/TypeTraits.h @@ -85,6 +85,16 @@ IsIntegral::Value || IsFloatingPointType::Value; }; +// Compile time type selection. +template struct Conditional { + using type = TrueT; +}; +template struct Conditional { + using type = FalseT; +}; +template +using ConditionalType = typename Conditional::type; + } // namespace cpp } // namespace __llvm_libc diff --git a/libc/src/string/memory_utils/address.h b/libc/src/string/memory_utils/address.h new file mode 100644 --- /dev/null +++ b/libc/src/string/memory_utils/address.h @@ -0,0 +1,133 @@ +//===-- Strongly typed address with alignment and access semantics --------===// +// +// 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_COMMON_H +#define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_COMMON_H + +#include "src/__support/CPP/TypeTraits.h" // cpp::ConditionalType +#include "src/string/memory_utils/utils.h" // is_power2 +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t + +namespace __llvm_libc { + +// Utility to enable static_assert(false) in templates. +template static void DeferredStaticAssert(const char *msg) { + static_assert(flag, "compilation error"); +} + +// A non-coercible type to represent raw data. +enum class ubyte : unsigned char { ZERO = 0 }; + +// Address attribute specifying whether the underlying load / store operations +// are temporal or non-temporal. +enum class Temporality { TEMPORAL, NON_TEMPORAL }; + +// Address attribute specifying whether the underlying load / store operations +// are aligned or unaligned. +enum class Aligned { NO, YES }; + +// Address attribute to discriminate between readable and writable addresses. +enum class Permission { Read, Write }; + +// Address is semantically equivalent to a pointer but also conveys compile time +// information that helps with instructions selection (aligned/unaligned, +// temporal/non-temporal). +template struct Address { + static_assert(is_power2(Alignment)); + static constexpr size_t ALIGNMENT = Alignment; + static constexpr Permission PERMISSION = P; + static constexpr Temporality TEMPORALITY = TS; + static constexpr bool IS_READ = P == Permission::Read; + static constexpr bool IS_WRITE = P == Permission::Write; + using PointeeType = cpp::ConditionalType; + using VoidType = cpp::ConditionalType; + + Address(VoidType *ptr) : ptr_(reinterpret_cast(ptr)) {} + + PointeeType *ptr() const { + return reinterpret_cast( + __builtin_assume_aligned(ptr_, ALIGNMENT)); + } + + PointeeType *const ptr_; + + template auto offset(size_t byte_offset) const { + static constexpr size_t NewAlignment = commonAlign(); + return Address(ptr_ + byte_offset); + } + +private: + static constexpr size_t gcd(size_t A, size_t B) { + return B == 0 ? A : gcd(B, A % B); + } + + template static constexpr size_t commonAlign() { + constexpr size_t GCD = gcd(ByteOffset, ALIGNMENT); + if constexpr (is_power2(GCD)) + return GCD; + else + return 1; + } +}; + +template struct IsAddressType : public cpp::FalseValue {}; +template +struct IsAddressType> : public cpp::TrueValue {}; + +// Reinterpret the address as a pointer to T. +// This is not UB since the underlying pointer always refers to a `char` in a +// buffer of raw data. +template static T *as(AddrT addr) { + static_assert(IsAddressType::Value); + return reinterpret_cast(addr.ptr()); +} + +// Offsets the address by a compile time amount, this allows propagating +// alignment whenever possible. +template +static auto offsetAddr(AddrT addr) { + static_assert(IsAddressType::Value); + return addr.template offset(ByteOffset); +} + +// Offsets the address by a runtime amount but assuming that the resulting +// address will be Alignment aligned. +template +static auto offsetAddrAssumeAligned(AddrT addr, size_t byte_offset) { + static_assert(IsAddressType::Value); + return Address(addr.ptr_ + + byte_offset); +} + +// Offsets the address by a runtime amount that is assumed to be a multiple of +// ByteOffset. This allows to propagate the address alignment whenever possible. +template +static auto offsetAddrMultiplesOf(AddrT addr, ptrdiff_t byte_offset) { + static_assert(IsAddressType::Value); + return addr.template offset(byte_offset); +} + +// User friendly aliases for common address types. +template +using SrcAddr = Address; +template +using DstAddr = Address; +template +using NtSrcAddr = + Address; +template +using NtDstAddr = + Address; + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_MEMORY_UTILS_COMMON_H diff --git a/libc/test/src/string/memory_utils/CMakeLists.txt b/libc/test/src/string/memory_utils/CMakeLists.txt --- a/libc/test/src/string/memory_utils/CMakeLists.txt +++ b/libc/test/src/string/memory_utils/CMakeLists.txt @@ -3,6 +3,7 @@ SUITE libc_string_unittests SRCS + address_test.cpp elements_test.cpp memory_access_test.cpp utils_test.cpp diff --git a/libc/test/src/string/memory_utils/address_test.cpp b/libc/test/src/string/memory_utils/address_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/string/memory_utils/address_test.cpp @@ -0,0 +1,80 @@ +#include "utils/UnitTest/Test.h" +#include + +namespace __llvm_libc { + +TEST(LlvmLibcAddress, AliasAreAddresses) { + ASSERT_TRUE(IsAddressType>::Value); + ASSERT_TRUE(IsAddressType>::Value); + ASSERT_TRUE(IsAddressType>::Value); + ASSERT_TRUE(IsAddressType>::Value); +} + +TEST(LlvmLibcAddress, AliasHaveRightPermissions) { + ASSERT_TRUE(SrcAddr<1>::IS_READ); + ASSERT_TRUE(NtSrcAddr<1>::IS_READ); + ASSERT_TRUE(DstAddr<1>::IS_WRITE); + ASSERT_TRUE(NtDstAddr<1>::IS_WRITE); +} + +TEST(LlvmLibcAddress, AliasHaveRightSemantic) { + ASSERT_EQ(SrcAddr<1>::TEMPORALITY, Temporality::TEMPORAL); + ASSERT_EQ(DstAddr<1>::TEMPORALITY, Temporality::TEMPORAL); + ASSERT_EQ(NtSrcAddr<1>::TEMPORALITY, Temporality::NON_TEMPORAL); + ASSERT_EQ(NtDstAddr<1>::TEMPORALITY, Temporality::NON_TEMPORAL); +} + +TEST(LlvmLibcAddress, AliasHaveRightAlignment) { + ASSERT_EQ(SrcAddr<1>::ALIGNMENT, size_t(1)); + ASSERT_EQ(SrcAddr<4>::ALIGNMENT, size_t(4)); +} + +TEST(LlvmLibcAddress, NarrowAlignment) { + // Address 8-byte aligned, offset by 8. + ASSERT_EQ(offsetAddr<8>(SrcAddr<8>(nullptr)).ALIGNMENT, 8UL); + // Address 16-byte aligned, offset by 4. + ASSERT_EQ(offsetAddr<4>(SrcAddr<16>(nullptr)).ALIGNMENT, 4UL); + // Address 4-byte aligned, offset by 16. + ASSERT_EQ(offsetAddr<16>(SrcAddr<4>(nullptr)).ALIGNMENT, 4UL); + // Address 4-byte aligned, offset by 1. + ASSERT_EQ(offsetAddr<1>(SrcAddr<4>(nullptr)).ALIGNMENT, 1UL); + // Address 4-byte aligned, offset by 2. + ASSERT_EQ(offsetAddr<2>(SrcAddr<4>(nullptr)).ALIGNMENT, 2UL); + // Address 4-byte aligned, offset by 6. + ASSERT_EQ(offsetAddr<6>(SrcAddr<4>(nullptr)).ALIGNMENT, 2UL); + // Address 4-byte aligned, offset by 10. + ASSERT_EQ(offsetAddr<10>(SrcAddr<4>(nullptr)).ALIGNMENT, 2UL); + // Address 8-byte aligned, offset by 6. + ASSERT_EQ(offsetAddr<6>(SrcAddr<8>(nullptr)).ALIGNMENT, 2UL); +} + +TEST(LlvmLibcAddress, OffsetAddr) { + ubyte a; + SrcAddr<1> addr(&a); + ASSERT_EQ((const void *)offsetAddr<4>(addr).ptr(), (const void *)(&a + 4)); + ASSERT_EQ((const void *)offsetAddr<32>(addr).ptr(), (const void *)(&a + 32)); +} + +TEST(LlvmLibcAddress, AssumeAligned) { + SrcAddr<16> addr(nullptr); + ASSERT_EQ(offsetAddrAssumeAligned<8>(addr, 0).ALIGNMENT, 8UL); + ASSERT_EQ(offsetAddrAssumeAligned<1>(addr, 0).ALIGNMENT, 1UL); + ASSERT_EQ(offsetAddrMultiplesOf<4>(addr, 0).ALIGNMENT, 4UL); + ASSERT_EQ(offsetAddrMultiplesOf<32>(addr, 0).ALIGNMENT, 16UL); +} + +TEST(LlvmLibcAddress, offsetAddrAssumeAligned) { + ubyte a; + SrcAddr<1> addr(&a); + ASSERT_EQ((const void *)offsetAddrAssumeAligned<1>(addr, 17).ptr(), + (const void *)(&a + 17)); +} + +TEST(LlvmLibcAddress, offsetAddrMultiplesOf) { + ubyte a; + SrcAddr<1> addr(&a); + ASSERT_EQ((const void *)offsetAddrMultiplesOf<4>(addr, 16).ptr(), + (const void *)(&a + 16)); +} + +} // namespace __llvm_libc