[libc-commits] [libc] [libc][time][windows] implement clock_getres (PR #118931)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Fri Dec 6 07:06:54 PST 2024


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/118931

>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 1/2] [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));
+}

>From 6d2fe1e665b4b70d8fd8624e4edb5ccae9acc781 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Fri, 6 Dec 2024 07:06:39 -0800
Subject: [PATCH 2/2] fix increment

---
 libc/src/time/windows/clock_getres.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libc/src/time/windows/clock_getres.cpp b/libc/src/time/windows/clock_getres.cpp
index 0a4994061e72c3..42b313e4f1adfc 100644
--- a/libc/src/time/windows/clock_getres.cpp
+++ b/libc/src/time/windows/clock_getres.cpp
@@ -72,8 +72,8 @@ LLVM_LIBC_FUNCTION(int, clock_getres, (clockid_t id, struct timespec *res)) {
       libc_errno = EINVAL;
       return -1;
     }
-    DWORD64 tv_sec = time_adjustment / HNS_PER_SEC;
-    DWORD64 tv_nsec = (time_adjustment % HNS_PER_SEC) * 100ULL;
+    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;
@@ -94,8 +94,8 @@ LLVM_LIBC_FUNCTION(int, clock_getres, (clockid_t id, struct timespec *res)) {
     }
     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;
+    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;



More information about the libc-commits mailing list