diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt index d0796b85aec2a..83a64e935cd02 100644 --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -98,6 +98,7 @@ set(TARGET_LIBC_ENTRYPOINTS # time.h entrypoints libc.src.time.time + libc.src.time.clock_getres ) set(TARGET_LIBM_ENTRYPOINTS diff --git a/libc/src/__support/time/windows/CMakeLists.txt b/libc/src/__support/time/windows/CMakeLists.txt index 0f557ed880080..dd0ac2f2f79ae 100644 --- a/libc/src/__support/time/windows/CMakeLists.txt +++ b/libc/src/__support/time/windows/CMakeLists.txt @@ -1,3 +1,12 @@ +add_header_library( + performance_counter + HDRS + performance_counter.h + DEPENDS + libc.src.__support.CPP.atomic + libc.src.__support.common +) + add_object_library( clock_gettime HDRS @@ -5,6 +14,7 @@ add_object_library( SRCS clock_gettime.cpp DEPENDS + .performance_counter libc.hdr.types.struct_timespec libc.hdr.types.clockid_t libc.hdr.errno_macros diff --git a/libc/src/__support/time/windows/clock_gettime.cpp b/libc/src/__support/time/windows/clock_gettime.cpp index c2536acf37d79..813eae0df9705 100644 --- a/libc/src/__support/time/windows/clock_gettime.cpp +++ b/libc/src/__support/time/windows/clock_gettime.cpp @@ -14,6 +14,7 @@ #include "src/__support/macros/optimization.h" #include "src/__support/time/clock_gettime.h" #include "src/__support/time/units.h" +#include "src/__support/time/windows/performance_counter.h" #define WIN32_LEAN_AND_MEAN #define NOMINMAX @@ -21,23 +22,6 @@ namespace LIBC_NAMESPACE_DECL { namespace internal { -static long long get_ticks_per_second() { - static cpp::Atomic frequency = 0; - // Relaxed ordering is enough. It is okay to record the frequency multiple - // times. The store operation itself is atomic and the value must propagate - // as required by cache coherence. - auto freq = frequency.load(cpp::MemoryOrder::RELAXED); - if (!freq) { - [[clang::uninitialized]] LARGE_INTEGER buffer; - // On systems that run Windows XP or later, the function will always - // succeed and will thus never return zero. - ::QueryPerformanceFrequency(&buffer); - frequency.store(buffer.QuadPart, cpp::MemoryOrder::RELAXED); - return buffer.QuadPart; - } - return freq; -} - ErrorOr clock_gettime(clockid_t clockid, timespec *ts) { using namespace time_units; constexpr unsigned long long HNS_PER_SEC = 1_s_ns / 100ULL; @@ -53,12 +37,12 @@ ErrorOr clock_gettime(clockid_t clockid, timespec *ts) { // see // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps // Is the performance counter monotonic (non-decreasing)? - // Yes. QPC does not go backward. + // Yes. performance_counter does not go backward. [[clang::uninitialized]] LARGE_INTEGER buffer; // On systems that run Windows XP or later, the function will always // succeed and will thus never return zero. ::QueryPerformanceCounter(&buffer); - long long freq = get_ticks_per_second(); + long long freq = performance_counter::get_ticks_per_second(); long long ticks = buffer.QuadPart; long long tv_sec = ticks / freq; long long tv_nsec = (ticks % freq) * 1_s_ns / freq; diff --git a/libc/src/__support/time/windows/performance_counter.h b/libc/src/__support/time/windows/performance_counter.h new file mode 100644 index 0000000000000..3df81b3efff40 --- /dev/null +++ b/libc/src/__support/time/windows/performance_counter.h @@ -0,0 +1,35 @@ +//===--- Cached Performance Counter Frequency ----------------------------===// +// +// 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/__support/CPP/atomic.h" +#include "src/__support/common.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +namespace LIBC_NAMESPACE_DECL { +namespace performance_counter { +LIBC_INLINE long long get_ticks_per_second() { + static cpp::Atomic frequency = 0; + // Relaxed ordering is enough. It is okay to record the frequency multiple + // times. The store operation itself is atomic and the value must propagate + // as required by cache coherence. + auto freq = frequency.load(cpp::MemoryOrder::RELAXED); + if (!freq) { + [[clang::uninitialized]] LARGE_INTEGER buffer; + // On systems that run Windows XP or later, the function will always + // succeed and will thus never return zero. + ::QueryPerformanceFrequency(&buffer); + frequency.store(buffer.QuadPart, cpp::MemoryOrder::RELAXED); + return buffer.QuadPart; + } + return freq; +} +} // namespace performance_counter +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt index 3e8e6882ffc5d..ae835dcc74274 100644 --- a/libc/src/time/CMakeLists.txt +++ b/libc/src/time/CMakeLists.txt @@ -151,3 +151,10 @@ add_entrypoint_object( DEPENDS .${LIBC_TARGET_OS}.gettimeofday ) + +add_entrypoint_object( + clock_getres + ALIAS + DEPENDS + .${LIBC_TARGET_OS}.clock_getres +) diff --git a/libc/src/time/clock_getres.h b/libc/src/time/clock_getres.h new file mode 100644 index 0000000000000..c1c581c535994 --- /dev/null +++ b/libc/src/time/clock_getres.h @@ -0,0 +1,21 @@ +//===-- Implementation header of clock_getres -------------------*- 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_TIME_CLOCK_GETRES_H +#define LLVM_LIBC_SRC_TIME_CLOCK_GETRES_H + +#include "hdr/types/clockid_t.h" +#include "hdr/types/struct_timespec.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +int clock_getres(clockid_t clockid, timespec *tp); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_TIME_CLOCK_GETRES_H diff --git a/libc/src/time/windows/CMakeLists.txt b/libc/src/time/windows/CMakeLists.txt new file mode 100644 index 0000000000000..6f242cd168f52 --- /dev/null +++ b/libc/src/time/windows/CMakeLists.txt @@ -0,0 +1,13 @@ +add_entrypoint_object( + clock_getres + SRCS + clock_getres.cpp + DEPENDS + libc.src.__support.time.windows.performance_counter + libc.src.__support.time.units + libc.src.__support.common + libc.src.__support.macros.optimization + libc.hdr.time_macros + libc.hdr.types.time_t + libc.hdr.types.struct_timespec +) diff --git a/libc/src/time/windows/clock_getres.cpp b/libc/src/time/windows/clock_getres.cpp new file mode 100644 index 0000000000000..b8c0c82aa6419 --- /dev/null +++ b/libc/src/time/windows/clock_getres.cpp @@ -0,0 +1,110 @@ +//===-- Windows implementation of clock_getres ------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "hdr/errno_macros.h" +#include "hdr/time_macros.h" +#include "hdr/types/clockid_t.h" +#include "hdr/types/struct_timespec.h" + +#include "src/__support/CPP/limits.h" +#include "src/__support/common.h" +#include "src/__support/macros/optimization.h" +#include "src/__support/time/units.h" +#include "src/__support/time/windows/performance_counter.h" +#include "src/errno/libc_errno.h" +#include "src/time/clock_getres.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include + +// add in dependencies for GetSystemTimeAdjustmentPrecise +#pragma comment(lib, "mincore.lib") + +namespace LIBC_NAMESPACE_DECL { +LLVM_LIBC_FUNCTION(int, clock_getres, (clockid_t id, struct timespec *res)) { + using namespace time_units; + // POSIX allows nullptr to be passed as res, in which case the function should + // do nothing. + if (res == nullptr) + return 0; + constexpr unsigned long long HNS_PER_SEC = 1_s_ns / 100ULL; + constexpr unsigned long long SEC_LIMIT = + cpp::numeric_limitstv_sec)>::max(); + // For CLOCK_MONOTONIC, we are using performance counter + // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps + // Hence, the resolution is given by the performance counter frequency. + // For CLOCK_REALTIME, the precision is given by + // GetSystemTimeAdjustmentPrecise + // (https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeadjustmentprecise) + // For CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID, the precision is + // given by GetSystemTimeAdjustment + // (https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeadjustment) + switch (id) { + default: + libc_errno = EINVAL; + return -1; + + case CLOCK_MONOTONIC: { + long long freq = performance_counter::get_ticks_per_second(); + __builtin_assume(freq != 0); + // division of 1 second by frequency, rounded up. + long long tv_sec = static_cast(freq == 1); + long long tv_nsec = + LIBC_LIKELY(freq != 1) ? 1ll + ((1_s_ns - 1ll) / freq) : 0ll; + // not possible to overflow tv_sec, tv_nsec + res->tv_sec = static_casttv_sec)>(tv_sec); + res->tv_nsec = static_casttv_nsec)>(tv_nsec); + break; + } + + case CLOCK_REALTIME: { + [[clang::uninitialized]] DWORD64 time_adjustment; + [[clang::uninitialized]] DWORD64 time_increment; + [[clang::uninitialized]] BOOL time_adjustment_disabled; + if (!::GetSystemTimeAdjustmentPrecise(&time_adjustment, &time_increment, + &time_adjustment_disabled)) { + libc_errno = EINVAL; + return -1; + } + DWORD64 tv_sec = time_increment / HNS_PER_SEC; + DWORD64 tv_nsec = (time_increment % HNS_PER_SEC) * 100ULL; + if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) { + libc_errno = EOVERFLOW; + return -1; + } + res->tv_sec = static_casttv_sec)>(tv_sec); + res->tv_nsec = static_casttv_nsec)>(tv_nsec); + break; + } + case CLOCK_PROCESS_CPUTIME_ID: + case CLOCK_THREAD_CPUTIME_ID: { + [[clang::uninitialized]] DWORD time_adjustment; + [[clang::uninitialized]] DWORD time_increment; + [[clang::uninitialized]] BOOL time_adjustment_disabled; + if (!::GetSystemTimeAdjustment(&time_adjustment, &time_increment, + &time_adjustment_disabled)) { + libc_errno = EINVAL; + return -1; + } + DWORD hns_per_sec = static_cast(HNS_PER_SEC); + DWORD sec_limit = static_cast(SEC_LIMIT); + DWORD tv_sec = time_increment / hns_per_sec; + DWORD tv_nsec = (time_increment % hns_per_sec) * 100UL; + if (LIBC_UNLIKELY(tv_sec > sec_limit)) { + libc_errno = EOVERFLOW; + return -1; + } + res->tv_sec = static_casttv_sec)>(tv_sec); + res->tv_nsec = static_casttv_nsec)>(tv_nsec); + break; + } + } + return 0; +} +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/test/src/time/CMakeLists.txt b/libc/test/src/time/CMakeLists.txt index d2a98677a5543..da3903f3e0e49 100644 --- a/libc/test/src/time/CMakeLists.txt +++ b/libc/test/src/time/CMakeLists.txt @@ -71,11 +71,21 @@ add_libc_test( SUITE libc_time_unittests SRCS - clock_gettime_test.cpp + clock_gettime_test.cpp DEPENDS libc.src.time.clock_gettime ) +add_libc_test( + clock_getres_test + SUITE + libc_time_unittests + SRCS + clock_getres_test.cpp + DEPENDS + libc.src.time.clock_getres +) + add_libc_unittest( difftime_test SUITE diff --git a/libc/test/src/time/clock_getres_test.cpp b/libc/test/src/time/clock_getres_test.cpp new file mode 100644 index 0000000000000..d8b3f01b37a38 --- /dev/null +++ b/libc/test/src/time/clock_getres_test.cpp @@ -0,0 +1,55 @@ +//===-- Unittests for clock_getres- ---------------------------------------===// +// +// 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 "hdr/time_macros.h" +#include "src/time/clock_getres.h" +#include "test/UnitTest/ErrnoSetterMatcher.h" +#include "test/UnitTest/Test.h" + +using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails; +using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds; + +TEST(LlvmLibcClockGetRes, Invalid) { + timespec tp; + EXPECT_THAT(LIBC_NAMESPACE::clock_getres(-1, &tp), Fails(EINVAL)); +} + +TEST(LlvmLibcClockGetRes, NullSpec) { + EXPECT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_REALTIME, nullptr), + Succeeds()); +} + +TEST(LlvmLibcClockGetRes, Realtime) { + timespec tp; + EXPECT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_REALTIME, &tp), Succeeds()); + EXPECT_GE(tp.tv_sec, static_cast(0)); + EXPECT_GE(tp.tv_nsec, static_cast(0)); +} + +TEST(LlvmLibcClockGetRes, Monotonic) { + timespec tp; + ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_MONOTONIC, &tp), Succeeds()); + EXPECT_GE(tp.tv_sec, static_cast(0)); + EXPECT_GE(tp.tv_nsec, static_cast(0)); +} + +TEST(LlvmLibcClockGetRes, ProcessCpuTime) { + timespec tp; + ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_PROCESS_CPUTIME_ID, &tp), + Succeeds()); + EXPECT_GE(tp.tv_sec, static_cast(0)); + EXPECT_GE(tp.tv_nsec, static_cast(0)); +} + +TEST(LlvmLibcClockGetRes, ThreadCpuTime) { + timespec tp; + ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_THREAD_CPUTIME_ID, &tp), + Succeeds()); + EXPECT_GE(tp.tv_sec, static_cast(0)); + EXPECT_GE(tp.tv_nsec, static_cast(0)); +}