Skip to content

[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

Merged
merged 4 commits into from
Dec 10, 2024

Conversation

SchrodingerZhu
Copy link
Contributor

This PR implements clock_getres for windows.

@llvmbot llvmbot added the libc label Dec 6, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 6, 2024

@llvm/pr-subscribers-libc

Author: Schrodinger ZHU Yifan (SchrodingerZhu)

Changes

This PR implements clock_getres for windows.


Full diff: https://github.com/llvm/llvm-project/pull/118931.diff

10 Files Affected:

  • (modified) libc/config/windows/entrypoints.txt (+1)
  • (modified) libc/src/__support/time/windows/CMakeLists.txt (+10)
  • (modified) libc/src/__support/time/windows/clock_gettime.cpp (+2-18)
  • (added) libc/src/__support/time/windows/qpc.h (+35)
  • (modified) libc/src/time/CMakeLists.txt (+7)
  • (added) libc/src/time/clock_getres.h (+21)
  • (added) libc/src/time/windows/CMakeLists.txt (+13)
  • (added) libc/src/time/windows/clock_getres.cpp (+110)
  • (modified) libc/test/src/time/CMakeLists.txt (+11-1)
  • (added) libc/test/src/time/clock_getres_test.cpp (+55)
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));
+}

@SchrodingerZhu
Copy link
Contributor Author

SchrodingerZhu commented Dec 6, 2024

should be increment. not adjustment. fixing it later

@@ -0,0 +1,35 @@
//===--- Cached QPC Frequency ----------------------------------*- C++ -*-===//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW #118553

Copy link
Contributor

@michaelrj-google michaelrj-google left a 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;
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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.

  2. Rounding up gives exact span from 1 to (1s - 1).

@SchrodingerZhu SchrodingerZhu merged commit 9a06fb7 into llvm:main Dec 10, 2024
9 of 10 checks passed
@SchrodingerZhu SchrodingerZhu deleted the libc/time/win/clock_getres branch December 10, 2024 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants