Skip to content

Commit

Permalink
[libc][time][windows] implement clock_getres (#118931)
Browse files Browse the repository at this point in the history
  • Loading branch information
SchrodingerZhu authored Dec 10, 2024
1 parent a6b5e18 commit 9a06fb7
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 20 deletions.
1 change: 1 addition & 0 deletions libc/config/windows/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ set(TARGET_LIBC_ENTRYPOINTS

# time.h entrypoints
libc.src.time.time
libc.src.time.clock_getres
)

set(TARGET_LIBM_ENTRYPOINTS
Expand Down
10 changes: 10 additions & 0 deletions libc/src/__support/time/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
add_header_library(
performance_counter
HDRS
performance_counter.h
DEPENDS
libc.src.__support.CPP.atomic
libc.src.__support.common
)

add_object_library(
clock_gettime
HDRS
../clock_gettime.h
SRCS
clock_gettime.cpp
DEPENDS
.performance_counter
libc.hdr.types.struct_timespec
libc.hdr.types.clockid_t
libc.hdr.errno_macros
Expand Down
22 changes: 3 additions & 19 deletions libc/src/__support/time/windows/clock_gettime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,14 @@
#include "src/__support/macros/optimization.h"
#include "src/__support/time/clock_gettime.h"
#include "src/__support/time/units.h"
#include "src/__support/time/windows/performance_counter.h"

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>

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;
Expand All @@ -53,12 +37,12 @@ ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
// see
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
// Is the performance counter monotonic (non-decreasing)?
// Yes. QPC does not go backward.
// Yes. performance_counter does not go backward.
[[clang::uninitialized]] LARGE_INTEGER buffer;
// 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 = performance_counter::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;
Expand Down
35 changes: 35 additions & 0 deletions libc/src/__support/time/windows/performance_counter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//===--- Cached Performance Counter Frequency ----------------------------===//
//
// 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 performance_counter {
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 performance_counter
} // namespace LIBC_NAMESPACE_DECL
7 changes: 7 additions & 0 deletions libc/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,10 @@ add_entrypoint_object(
DEPENDS
.${LIBC_TARGET_OS}.gettimeofday
)

add_entrypoint_object(
clock_getres
ALIAS
DEPENDS
.${LIBC_TARGET_OS}.clock_getres
)
21 changes: 21 additions & 0 deletions libc/src/time/clock_getres.h
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions libc/src/time/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
add_entrypoint_object(
clock_getres
SRCS
clock_getres.cpp
DEPENDS
libc.src.__support.time.windows.performance_counter
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
)
110 changes: 110 additions & 0 deletions libc/src/time/windows/clock_getres.cpp
Original file line number Diff line number Diff line change
@@ -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/performance_counter.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 performance counter
// https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
// Hence, the resolution is given by the performance counter 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 = performance_counter::get_ticks_per_second();
__builtin_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_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;
}
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_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;
}
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
12 changes: 11 additions & 1 deletion libc/test/src/time/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions libc/test/src/time/clock_getres_test.cpp
Original file line number Diff line number Diff line change
@@ -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));
}

0 comments on commit 9a06fb7

Please sign in to comment.