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 @@ -15,7 +15,9 @@ namespace cpp { template struct EnableIf; -template struct EnableIf { typedef T Type; }; +template struct EnableIf { + typedef T Type; +}; template using EnableIfType = typename EnableIf::Type; @@ -28,7 +30,9 @@ static constexpr bool Value = false; }; -template struct TypeIdentity { typedef T Type; }; +template struct TypeIdentity { + typedef T Type; +}; template struct IsSame : public FalseValue {}; template struct IsSame : public TrueValue {}; @@ -59,6 +63,10 @@ ; }; +template struct IsEnum { + static constexpr bool Value = __is_enum(Type); +}; + template struct IsPointerTypeNoCV : public FalseValue {}; template struct IsPointerTypeNoCV : public TrueValue {}; template struct IsPointerType { @@ -77,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,136 @@ +//===-- 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: + template static constexpr size_t commonAlign() { + constexpr size_t GCD = gcd(); + if constexpr (is_power2(GCD)) + return GCD; + else + return 1; + } + + template static constexpr size_t gcd() { + if constexpr (B == 0) + return A; + else + return gcd(); + } +}; + +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 diff --git a/libc/utils/UnitTest/LibcTest.h b/libc/utils/UnitTest/LibcTest.h --- a/libc/utils/UnitTest/LibcTest.h +++ b/libc/utils/UnitTest/LibcTest.h @@ -89,6 +89,14 @@ return internal::test(Ctx, Cond, LHS, RHS, LHSStr, RHSStr, File, Line); } + template ::Value, int> = 0> + bool test(TestCondition Cond, ValType LHS, ValType RHS, const char *LHSStr, + const char *RHSStr, const char *File, unsigned long Line) { + return internal::test(Ctx, Cond, (long long)LHS, (long long)RHS, LHSStr, + RHSStr, File, Line); + } + template < typename ValType, cpp::EnableIfType::Value, ValType> = nullptr>