-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc][time][windows] implement clock_getres #118931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[libc][time][windows] implement clock_getres #118931
Conversation
@llvm/pr-subscribers-libc Author: Schrodinger ZHU Yifan (SchrodingerZhu) ChangesThis PR implements Full diff: https://github.com/llvm/llvm-project/pull/118931.diff 10 Files Affected:
diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt
index d0796b85aec2af..83a64e935cd02c 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 0f557ed8800802..31abc2d552661d 100644
--- a/libc/src/__support/time/windows/CMakeLists.txt
+++ b/libc/src/__support/time/windows/CMakeLists.txt
@@ -1,3 +1,12 @@
+add_header_library(
+ qpc
+ HDRS
+ qpc.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
+ .qpc
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 c2536acf37d799..6c60ffd2c4af06 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/qpc.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<long long> 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<int> clock_gettime(clockid_t clockid, timespec *ts) {
using namespace time_units;
constexpr unsigned long long HNS_PER_SEC = 1_s_ns / 100ULL;
@@ -58,7 +42,7 @@ ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
// 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 = qpc::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/qpc.h b/libc/src/__support/time/windows/qpc.h
new file mode 100644
index 00000000000000..ff803a0858a6eb
--- /dev/null
+++ b/libc/src/__support/time/windows/qpc.h
@@ -0,0 +1,35 @@
+//===--- Cached QPC Frequency ----------------------------------*- 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 "src/__support/CPP/atomic.h"
+#include "src/__support/common.h"
+
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+
+namespace LIBC_NAMESPACE_DECL {
+namespace qpc {
+LIBC_INLINE long long get_ticks_per_second() {
+ static cpp::Atomic<long long> 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 qpc
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/CMakeLists.txt b/libc/src/time/CMakeLists.txt
index 3e8e6882ffc5d4..ae835dcc742742 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 00000000000000..c1c581c5359949
--- /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 00000000000000..69fa95e82f3c3f
--- /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.qpc
+ 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 00000000000000..0a4994061e72c3
--- /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/qpc.h"
+#include "src/errno/libc_errno.h"
+#include "src/time/clock_getres.h"
+
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+
+// 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_limits<decltype(res->tv_sec)>::max();
+ // For CLOCK_MONOTONIC, we are using QPC
+ // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
+ // Hence, the resolution is given by the QPC 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 = qpc::get_ticks_per_second();
+ [[clang::assume(freq != 0)]];
+ // division of 1 second by frequency, rounded up.
+ long long tv_sec = static_cast<long long>(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_cast<decltype(res->tv_sec)>(tv_sec);
+ res->tv_nsec = static_cast<decltype(res->tv_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_adjustment / HNS_PER_SEC;
+ DWORD64 tv_nsec = (time_adjustment % HNS_PER_SEC) * 100ULL;
+ if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) {
+ libc_errno = EOVERFLOW;
+ return -1;
+ }
+ res->tv_sec = static_cast<decltype(res->tv_sec)>(tv_sec);
+ res->tv_nsec = static_cast<decltype(res->tv_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<DWORD>(HNS_PER_SEC);
+ DWORD sec_limit = static_cast<DWORD>(SEC_LIMIT);
+ DWORD tv_sec = time_adjustment / hns_per_sec;
+ DWORD tv_nsec = (time_adjustment % hns_per_sec) * 100UL;
+ if (LIBC_UNLIKELY(tv_sec > sec_limit)) {
+ libc_errno = EOVERFLOW;
+ return -1;
+ }
+ res->tv_sec = static_cast<decltype(res->tv_sec)>(tv_sec);
+ res->tv_nsec = static_cast<decltype(res->tv_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 d2a98677a55435..da3903f3e0e494 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 00000000000000..d8b3f01b37a38a
--- /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<decltype(tp.tv_sec)>(0));
+ EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
+}
+
+TEST(LlvmLibcClockGetRes, Monotonic) {
+ timespec tp;
+ ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_MONOTONIC, &tp), Succeeds());
+ EXPECT_GE(tp.tv_sec, static_cast<decltype(tp.tv_sec)>(0));
+ EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(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<decltype(tp.tv_sec)>(0));
+ EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(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<decltype(tp.tv_sec)>(0));
+ EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
+}
|
should be increment. not adjustment. fixing it later |
@@ -0,0 +1,35 @@ | |||
//===--- Cached QPC Frequency ----------------------------------*- C++ -*-===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW #118553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM
// division of 1 second by frequency, rounded up. | ||
long long tv_sec = static_cast<long long>(freq == 1); | ||
long long tv_nsec = | ||
LIBC_LIKELY(freq != 1) ? 1ll + ((1_s_ns - 1ll) / freq) : 0ll; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious: is there any reason we have to explicitly round the division results up here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Resolution is the unit of time that should be increasable for API like clock_settime. In fact, the manpage saids that clock_settime always truncate the time to multiples of the resolution. If we round down, user may expect a finer unit of increment is feasible, thus propagating to further issues.
-
Rounding up gives exact span from 1 to (1s - 1).
This PR implements
clock_getres
for windows.