[libc-commits] [libc] [libc][time][windows] implement clock_getres (PR #118931)
Schrodinger ZHU Yifan via libc-commits
libc-commits at lists.llvm.org
Thu Dec 5 22:30:43 PST 2024
https://github.com/SchrodingerZhu created https://github.com/llvm/llvm-project/pull/118931
This PR implements `clock_getres` for windows.
>From f56e12c94dd58b624a9c7b047e529ba6190be3d4 Mon Sep 17 00:00:00 2001
From: schrodingerzhu <i at zhuyi.fan>
Date: Fri, 6 Dec 2024 01:27:31 -0500
Subject: [PATCH] [libc][time][windows] implement clock_getres
---
libc/config/windows/entrypoints.txt | 1 +
.../src/__support/time/windows/CMakeLists.txt | 10 ++
.../__support/time/windows/clock_gettime.cpp | 20 +---
libc/src/__support/time/windows/qpc.h | 35 ++++++
libc/src/time/CMakeLists.txt | 7 ++
libc/src/time/clock_getres.h | 21 ++++
libc/src/time/windows/CMakeLists.txt | 13 +++
libc/src/time/windows/clock_getres.cpp | 110 ++++++++++++++++++
libc/test/src/time/CMakeLists.txt | 12 +-
libc/test/src/time/clock_getres_test.cpp | 55 +++++++++
10 files changed, 265 insertions(+), 19 deletions(-)
create mode 100644 libc/src/__support/time/windows/qpc.h
create mode 100644 libc/src/time/clock_getres.h
create mode 100644 libc/src/time/windows/CMakeLists.txt
create mode 100644 libc/src/time/windows/clock_getres.cpp
create mode 100644 libc/test/src/time/clock_getres_test.cpp
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));
+}
More information about the libc-commits
mailing list