Skip to content

Commit 9a06fb7

Browse files
[libc][time][windows] implement clock_getres (#118931)
1 parent a6b5e18 commit 9a06fb7

File tree

10 files changed

+266
-20
lines changed

10 files changed

+266
-20
lines changed

libc/config/windows/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ set(TARGET_LIBC_ENTRYPOINTS
9898

9999
# time.h entrypoints
100100
libc.src.time.time
101+
libc.src.time.clock_getres
101102
)
102103

103104
set(TARGET_LIBM_ENTRYPOINTS

libc/src/__support/time/windows/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1+
add_header_library(
2+
performance_counter
3+
HDRS
4+
performance_counter.h
5+
DEPENDS
6+
libc.src.__support.CPP.atomic
7+
libc.src.__support.common
8+
)
9+
110
add_object_library(
211
clock_gettime
312
HDRS
413
../clock_gettime.h
514
SRCS
615
clock_gettime.cpp
716
DEPENDS
17+
.performance_counter
818
libc.hdr.types.struct_timespec
919
libc.hdr.types.clockid_t
1020
libc.hdr.errno_macros

libc/src/__support/time/windows/clock_gettime.cpp

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,14 @@
1414
#include "src/__support/macros/optimization.h"
1515
#include "src/__support/time/clock_gettime.h"
1616
#include "src/__support/time/units.h"
17+
#include "src/__support/time/windows/performance_counter.h"
1718

1819
#define WIN32_LEAN_AND_MEAN
1920
#define NOMINMAX
2021
#include <Windows.h>
2122

2223
namespace LIBC_NAMESPACE_DECL {
2324
namespace internal {
24-
static long long get_ticks_per_second() {
25-
static cpp::Atomic<long long> frequency = 0;
26-
// Relaxed ordering is enough. It is okay to record the frequency multiple
27-
// times. The store operation itself is atomic and the value must propagate
28-
// as required by cache coherence.
29-
auto freq = frequency.load(cpp::MemoryOrder::RELAXED);
30-
if (!freq) {
31-
[[clang::uninitialized]] LARGE_INTEGER buffer;
32-
// On systems that run Windows XP or later, the function will always
33-
// succeed and will thus never return zero.
34-
::QueryPerformanceFrequency(&buffer);
35-
frequency.store(buffer.QuadPart, cpp::MemoryOrder::RELAXED);
36-
return buffer.QuadPart;
37-
}
38-
return freq;
39-
}
40-
4125
ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
4226
using namespace time_units;
4327
constexpr unsigned long long HNS_PER_SEC = 1_s_ns / 100ULL;
@@ -53,12 +37,12 @@ ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
5337
// see
5438
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
5539
// Is the performance counter monotonic (non-decreasing)?
56-
// Yes. QPC does not go backward.
40+
// Yes. performance_counter does not go backward.
5741
[[clang::uninitialized]] LARGE_INTEGER buffer;
5842
// On systems that run Windows XP or later, the function will always
5943
// succeed and will thus never return zero.
6044
::QueryPerformanceCounter(&buffer);
61-
long long freq = get_ticks_per_second();
45+
long long freq = performance_counter::get_ticks_per_second();
6246
long long ticks = buffer.QuadPart;
6347
long long tv_sec = ticks / freq;
6448
long long tv_nsec = (ticks % freq) * 1_s_ns / freq;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//===--- Cached Performance Counter Frequency ----------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/__support/CPP/atomic.h"
10+
#include "src/__support/common.h"
11+
12+
#define WIN32_LEAN_AND_MEAN
13+
#define NOMINMAX
14+
#include <Windows.h>
15+
16+
namespace LIBC_NAMESPACE_DECL {
17+
namespace performance_counter {
18+
LIBC_INLINE long long get_ticks_per_second() {
19+
static cpp::Atomic<long long> frequency = 0;
20+
// Relaxed ordering is enough. It is okay to record the frequency multiple
21+
// times. The store operation itself is atomic and the value must propagate
22+
// as required by cache coherence.
23+
auto freq = frequency.load(cpp::MemoryOrder::RELAXED);
24+
if (!freq) {
25+
[[clang::uninitialized]] LARGE_INTEGER buffer;
26+
// On systems that run Windows XP or later, the function will always
27+
// succeed and will thus never return zero.
28+
::QueryPerformanceFrequency(&buffer);
29+
frequency.store(buffer.QuadPart, cpp::MemoryOrder::RELAXED);
30+
return buffer.QuadPart;
31+
}
32+
return freq;
33+
}
34+
} // namespace performance_counter
35+
} // namespace LIBC_NAMESPACE_DECL

libc/src/time/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,10 @@ add_entrypoint_object(
151151
DEPENDS
152152
.${LIBC_TARGET_OS}.gettimeofday
153153
)
154+
155+
add_entrypoint_object(
156+
clock_getres
157+
ALIAS
158+
DEPENDS
159+
.${LIBC_TARGET_OS}.clock_getres
160+
)

libc/src/time/clock_getres.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===-- Implementation header of clock_getres -------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#ifndef LLVM_LIBC_SRC_TIME_CLOCK_GETRES_H
9+
#define LLVM_LIBC_SRC_TIME_CLOCK_GETRES_H
10+
11+
#include "hdr/types/clockid_t.h"
12+
#include "hdr/types/struct_timespec.h"
13+
#include "src/__support/macros/config.h"
14+
15+
namespace LIBC_NAMESPACE_DECL {
16+
17+
int clock_getres(clockid_t clockid, timespec *tp);
18+
19+
} // namespace LIBC_NAMESPACE_DECL
20+
21+
#endif // LLVM_LIBC_SRC_TIME_CLOCK_GETRES_H

libc/src/time/windows/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
add_entrypoint_object(
2+
clock_getres
3+
SRCS
4+
clock_getres.cpp
5+
DEPENDS
6+
libc.src.__support.time.windows.performance_counter
7+
libc.src.__support.time.units
8+
libc.src.__support.common
9+
libc.src.__support.macros.optimization
10+
libc.hdr.time_macros
11+
libc.hdr.types.time_t
12+
libc.hdr.types.struct_timespec
13+
)
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//===-- Windows implementation of clock_getres ------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "hdr/errno_macros.h"
10+
#include "hdr/time_macros.h"
11+
#include "hdr/types/clockid_t.h"
12+
#include "hdr/types/struct_timespec.h"
13+
14+
#include "src/__support/CPP/limits.h"
15+
#include "src/__support/common.h"
16+
#include "src/__support/macros/optimization.h"
17+
#include "src/__support/time/units.h"
18+
#include "src/__support/time/windows/performance_counter.h"
19+
#include "src/errno/libc_errno.h"
20+
#include "src/time/clock_getres.h"
21+
22+
#define WIN32_LEAN_AND_MEAN
23+
#define NOMINMAX
24+
#include <Windows.h>
25+
26+
// add in dependencies for GetSystemTimeAdjustmentPrecise
27+
#pragma comment(lib, "mincore.lib")
28+
29+
namespace LIBC_NAMESPACE_DECL {
30+
LLVM_LIBC_FUNCTION(int, clock_getres, (clockid_t id, struct timespec *res)) {
31+
using namespace time_units;
32+
// POSIX allows nullptr to be passed as res, in which case the function should
33+
// do nothing.
34+
if (res == nullptr)
35+
return 0;
36+
constexpr unsigned long long HNS_PER_SEC = 1_s_ns / 100ULL;
37+
constexpr unsigned long long SEC_LIMIT =
38+
cpp::numeric_limits<decltype(res->tv_sec)>::max();
39+
// For CLOCK_MONOTONIC, we are using performance counter
40+
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
41+
// Hence, the resolution is given by the performance counter frequency.
42+
// For CLOCK_REALTIME, the precision is given by
43+
// GetSystemTimeAdjustmentPrecise
44+
// (https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeadjustmentprecise)
45+
// For CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID, the precision is
46+
// given by GetSystemTimeAdjustment
47+
// (https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeadjustment)
48+
switch (id) {
49+
default:
50+
libc_errno = EINVAL;
51+
return -1;
52+
53+
case CLOCK_MONOTONIC: {
54+
long long freq = performance_counter::get_ticks_per_second();
55+
__builtin_assume(freq != 0);
56+
// division of 1 second by frequency, rounded up.
57+
long long tv_sec = static_cast<long long>(freq == 1);
58+
long long tv_nsec =
59+
LIBC_LIKELY(freq != 1) ? 1ll + ((1_s_ns - 1ll) / freq) : 0ll;
60+
// not possible to overflow tv_sec, tv_nsec
61+
res->tv_sec = static_cast<decltype(res->tv_sec)>(tv_sec);
62+
res->tv_nsec = static_cast<decltype(res->tv_nsec)>(tv_nsec);
63+
break;
64+
}
65+
66+
case CLOCK_REALTIME: {
67+
[[clang::uninitialized]] DWORD64 time_adjustment;
68+
[[clang::uninitialized]] DWORD64 time_increment;
69+
[[clang::uninitialized]] BOOL time_adjustment_disabled;
70+
if (!::GetSystemTimeAdjustmentPrecise(&time_adjustment, &time_increment,
71+
&time_adjustment_disabled)) {
72+
libc_errno = EINVAL;
73+
return -1;
74+
}
75+
DWORD64 tv_sec = time_increment / HNS_PER_SEC;
76+
DWORD64 tv_nsec = (time_increment % HNS_PER_SEC) * 100ULL;
77+
if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) {
78+
libc_errno = EOVERFLOW;
79+
return -1;
80+
}
81+
res->tv_sec = static_cast<decltype(res->tv_sec)>(tv_sec);
82+
res->tv_nsec = static_cast<decltype(res->tv_nsec)>(tv_nsec);
83+
break;
84+
}
85+
case CLOCK_PROCESS_CPUTIME_ID:
86+
case CLOCK_THREAD_CPUTIME_ID: {
87+
[[clang::uninitialized]] DWORD time_adjustment;
88+
[[clang::uninitialized]] DWORD time_increment;
89+
[[clang::uninitialized]] BOOL time_adjustment_disabled;
90+
if (!::GetSystemTimeAdjustment(&time_adjustment, &time_increment,
91+
&time_adjustment_disabled)) {
92+
libc_errno = EINVAL;
93+
return -1;
94+
}
95+
DWORD hns_per_sec = static_cast<DWORD>(HNS_PER_SEC);
96+
DWORD sec_limit = static_cast<DWORD>(SEC_LIMIT);
97+
DWORD tv_sec = time_increment / hns_per_sec;
98+
DWORD tv_nsec = (time_increment % hns_per_sec) * 100UL;
99+
if (LIBC_UNLIKELY(tv_sec > sec_limit)) {
100+
libc_errno = EOVERFLOW;
101+
return -1;
102+
}
103+
res->tv_sec = static_cast<decltype(res->tv_sec)>(tv_sec);
104+
res->tv_nsec = static_cast<decltype(res->tv_nsec)>(tv_nsec);
105+
break;
106+
}
107+
}
108+
return 0;
109+
}
110+
} // namespace LIBC_NAMESPACE_DECL

libc/test/src/time/CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,21 @@ add_libc_test(
7171
SUITE
7272
libc_time_unittests
7373
SRCS
74-
clock_gettime_test.cpp
74+
clock_gettime_test.cpp
7575
DEPENDS
7676
libc.src.time.clock_gettime
7777
)
7878

79+
add_libc_test(
80+
clock_getres_test
81+
SUITE
82+
libc_time_unittests
83+
SRCS
84+
clock_getres_test.cpp
85+
DEPENDS
86+
libc.src.time.clock_getres
87+
)
88+
7989
add_libc_unittest(
8090
difftime_test
8191
SUITE
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===-- Unittests for clock_getres- ---------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "hdr/time_macros.h"
10+
#include "src/time/clock_getres.h"
11+
#include "test/UnitTest/ErrnoSetterMatcher.h"
12+
#include "test/UnitTest/Test.h"
13+
14+
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
15+
using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
16+
17+
TEST(LlvmLibcClockGetRes, Invalid) {
18+
timespec tp;
19+
EXPECT_THAT(LIBC_NAMESPACE::clock_getres(-1, &tp), Fails(EINVAL));
20+
}
21+
22+
TEST(LlvmLibcClockGetRes, NullSpec) {
23+
EXPECT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_REALTIME, nullptr),
24+
Succeeds());
25+
}
26+
27+
TEST(LlvmLibcClockGetRes, Realtime) {
28+
timespec tp;
29+
EXPECT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_REALTIME, &tp), Succeeds());
30+
EXPECT_GE(tp.tv_sec, static_cast<decltype(tp.tv_sec)>(0));
31+
EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
32+
}
33+
34+
TEST(LlvmLibcClockGetRes, Monotonic) {
35+
timespec tp;
36+
ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_MONOTONIC, &tp), Succeeds());
37+
EXPECT_GE(tp.tv_sec, static_cast<decltype(tp.tv_sec)>(0));
38+
EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
39+
}
40+
41+
TEST(LlvmLibcClockGetRes, ProcessCpuTime) {
42+
timespec tp;
43+
ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_PROCESS_CPUTIME_ID, &tp),
44+
Succeeds());
45+
EXPECT_GE(tp.tv_sec, static_cast<decltype(tp.tv_sec)>(0));
46+
EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
47+
}
48+
49+
TEST(LlvmLibcClockGetRes, ThreadCpuTime) {
50+
timespec tp;
51+
ASSERT_THAT(LIBC_NAMESPACE::clock_getres(CLOCK_THREAD_CPUTIME_ID, &tp),
52+
Succeeds());
53+
EXPECT_GE(tp.tv_sec, static_cast<decltype(tp.tv_sec)>(0));
54+
EXPECT_GE(tp.tv_nsec, static_cast<decltype(tp.tv_nsec)>(0));
55+
}

0 commit comments

Comments
 (0)