diff --git a/libc/cmake/modules/LLVMLibCRules.cmake b/libc/cmake/modules/LLVMLibCRules.cmake --- a/libc/cmake/modules/LLVMLibCRules.cmake +++ b/libc/cmake/modules/LLVMLibCRules.cmake @@ -317,18 +317,22 @@ set(library_deps "") foreach(dep IN LISTS LIBC_UNITTEST_DEPENDS) - get_target_property(dep_type ${dep} "TARGET_TYPE") - if (dep_type) - string(COMPARE EQUAL ${dep_type} ${ENTRYPOINT_OBJ_TARGET_TYPE} dep_is_entrypoint) - if(dep_is_entrypoint) - get_target_property(obj_file ${dep} "OBJECT_FILE_RAW") - list(APPEND library_deps ${obj_file}) - continue() + # If the dep is a normal CMake library target then add it to the list of + # library_deps. + get_property(dep_type TARGET ${dep} PROPERTY "TYPE") + if (${dep_type} MATCHES "_LIBRARY$") + list(APPEND library_deps ${dep}) + else() + get_property(dep_target_type TARGET ${dep} PROPERTY "TARGET_TYPE") + if (dep_target_type) + string(COMPARE EQUAL ${dep_target_type} ${ENTRYPOINT_OBJ_TARGET_TYPE} dep_is_entrypoint) + if(dep_is_entrypoint) + get_target_property(obj_file ${dep} "OBJECT_FILE_RAW") + list(APPEND library_deps ${obj_file}) + endif() endif() endif() - # TODO: Check if the dep is a normal CMake library target. If yes, then add it - # to the list of library_deps. - endforeach(dep) + endforeach() add_executable( ${target_name} diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt --- a/libc/src/CMakeLists.txt +++ b/libc/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(errno) add_subdirectory(math) add_subdirectory(string) +add_subdirectory(memory) # TODO: Add this target conditional to the target OS. add_subdirectory(sys) diff --git a/libc/src/memory/CMakeLists.txt b/libc/src/memory/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/src/memory/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(memory_constants INTERFACE) +target_sources(memory_constants INTERFACE "$") +target_include_directories(memory_constants INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +add_library(memory_utils INTERFACE) +target_sources(memory_utils INTERFACE "$") +target_include_directories(memory_utils INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(memory_utils INTERFACE memory_constants) diff --git a/libc/src/memory/constants.h b/libc/src/memory/constants.h new file mode 100644 --- /dev/null +++ b/libc/src/memory/constants.h @@ -0,0 +1,56 @@ +//===-------------------------- Memory constants --------------------------===// +// +// 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_CONSTANTS_H +#define LLVM_LIBC_SRC_MEMORY_CONSTANTS_H + +// LLVM_LIBC_CACHELINE_SIZE +// +// Explicitly defines the size of the L1 cache for purposes of alignment. +// Setting the cacheline size allows you to specify that certain objects be +// aligned on a cacheline boundary with `ABSL_CACHELINE_ALIGNED` declarations. +// (See below.) +// +// NOTE: this macro should be replaced with the following C++17 features, when +// those are generally available: +// +// * `std::hardware_constructive_interference_size` +// * `std::hardware_destructive_interference_size` +// +// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0154r1.html +// for more information. +#if defined(__GNUC__) +// Cache line alignment +#if defined(__i386__) || defined(__x86_64__) +#define LLVM_LIBC_CACHELINE_SIZE 64 +#elif defined(__powerpc64__) +#define LLVM_LIBC_CACHELINE_SIZE 128 +#elif defined(__aarch64__) +// We would need to read special register ctr_el0 to find out L1 dcache size. +// This value is a good estimate based on a real aarch64 machine. +#define LLVM_LIBC_CACHELINE_SIZE 64 +#elif defined(__arm__) +// Cache line sizes for ARM: These values are not strictly correct since +// cache line sizes depend on implementations, not architectures. There +// are even implementations with cache line sizes configurable at boot +// time. +#if defined(__ARM_ARCH_5T__) +#define LLVM_LIBC_CACHELINE_SIZE 32 +#elif defined(__ARM_ARCH_7A__) +#define LLVM_LIBC_CACHELINE_SIZE 64 +#endif +#endif +#endif + +#ifndef LLVM_LIBC_CACHELINE_SIZE +// A reasonable default guess. Note that overestimates tend to waste more +// space, while underestimates tend to waste more time. +#define LLVM_LIBC_CACHELINE_SIZE 64 +#endif + +#endif // LLVM_LIBC_SRC_MEMORY_CONSTANTS_H diff --git a/libc/src/memory/utils.h b/libc/src/memory/utils.h new file mode 100644 --- /dev/null +++ b/libc/src/memory/utils.h @@ -0,0 +1,65 @@ +//===---------------------------- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MEMOR_UTILS_H +#define LLVM_LIBC_SRC_MEMOR_UTILS_H + +#include "constants.h" + +#include +#include + +namespace __llvm_libc { + +// Making sure these functions are inlined by using a private namespace +namespace { + +// Return whether `value` is zero or a power of two. +constexpr bool is_power2_or_zero(size_t value) { + return (value & (value - 1U)) == 0; +} + +// Return whether `value` is a power of two. +constexpr bool is_power2(size_t value) { + return value && is_power2_or_zero(value); +} + +// Compile time version of log2 that handles 0. +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). +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). +constexpr size_t ge_power2(size_t value) { + return is_power2_or_zero(value) ? value : 1ULL << (log2(value) + 1); +} + +template intptr_t offset_to_next_aligned(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 offset from `ptr` to the next cache line. +inline intptr_t offset_to_next_cache_line(const void *ptr) { + return offset_to_next_aligned(ptr); +} + +} // namespace + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MEMOR_UTILS_H diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(errno) +add_subdirectory(memory) add_subdirectory(string) add_subdirectory(sys) diff --git a/libc/test/src/memory/CMakeLists.txt b/libc/test/src/memory/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/libc/test/src/memory/CMakeLists.txt @@ -0,0 +1,11 @@ +add_libc_testsuite(libc_memory_unittests) + +add_libc_unittest( + utils_test + SUITE + libc_memory_unittests + SRCS + utils_test.cpp + DEPENDS + memory_utils +) diff --git a/libc/test/src/memory/utils_test.cpp b/libc/test/src/memory/utils_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/memory/utils_test.cpp @@ -0,0 +1,102 @@ +//===-------------------- 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/memory/utils.h" +#include "utils/UnitTest/Test.h" + +#include + +namespace __llvm_libc { + +TEST(UtilsTest, IsPowerOfTwoOrZero) { + static const std::array kExpectedValues({ + 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 0-15 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 + 1 // 64 + }); + for (size_t i = 0; i < kExpectedValues.size(); ++i) { + ASSERT_EQ((int)is_power2_or_zero(i), kExpectedValues[i]); + } +} + +TEST(UtilsTest, IsPowerOfTwo) { + static const std::array kExpectedValues({ + 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 0-15 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 48-63 + 1 // 64 + }); + for (size_t i = 0; i < kExpectedValues.size(); ++i) { + ASSERT_EQ((int)is_power2(i), kExpectedValues[i]); + } +} + +TEST(UtilsTest, Log2) { + static const std::array kExpectedValues({ + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // 0-15 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 16-31 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 32-47 + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 48-63 + 6 // 64 + }); + for (size_t i = 0; i < kExpectedValues.size(); ++i) { + ASSERT_EQ(log2(i), kExpectedValues[i]); + } +} + +TEST(UtilsTest, LEPowerOf2) { + static const std::array kExpectedValues({ + 0, 1, 2, 2, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, // 0-15 + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, // 16-31 + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // 32-47 + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // 48-63 + 64 // 64 + }); + for (size_t i = 0; i < kExpectedValues.size(); ++i) { + ASSERT_EQ(le_power2(i), kExpectedValues[i]); + } +} + +TEST(UtilsTest, GEPowerOf2) { + static const std::array kExpectedValues({ + 0, 1, 2, 4, 4, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16, 16, // 0-15 + 16, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // 16-31 + 32, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, // 48-63 + 64, 128 // 64-65 + }); + for (size_t i = 0; i < kExpectedValues.size(); ++i) { + ASSERT_EQ(ge_power2(i), kExpectedValues[i]); + } +} + +TEST(UtilsTest, OffsetToNextAligned) { + const auto forge = [](size_t offset) -> const void * { + return reinterpret_cast(offset); + }; + ASSERT_EQ(offset_to_next_aligned<16>(forge(0)), 0L); + ASSERT_EQ(offset_to_next_aligned<16>(forge(1)), 15L); + ASSERT_EQ(offset_to_next_aligned<16>(forge(16)), 0L); + ASSERT_EQ(offset_to_next_aligned<16>(forge(15)), 1L); + ASSERT_EQ(offset_to_next_aligned<32>(forge(16)), 16L); +} + +TEST(UtilsTest, OffsetToNextCacheLine) { + ASSERT_GT(LLVM_LIBC_CACHELINE_SIZE, 0); + const auto forge = [](size_t offset) -> const void * { + return reinterpret_cast(offset); + }; + ASSERT_EQ(offset_to_next_cache_line(forge(0)), 0L); + ASSERT_EQ(offset_to_next_cache_line(forge(1)), LLVM_LIBC_CACHELINE_SIZE - 1L); + ASSERT_EQ(offset_to_next_cache_line(forge(LLVM_LIBC_CACHELINE_SIZE)), 0L); + ASSERT_EQ(offset_to_next_cache_line(forge(LLVM_LIBC_CACHELINE_SIZE - 1)), 1L); +} +} // namespace __llvm_libc