[libc][windows] start time API implementation (#117775)

Add a `clock_gettime` emulation layer and use it to implement the `time`
entrypoint.

For windows, the monotonic clock is emulated using `QPC`.
The realtime clock is emulated using `GetSystemTimePreciseAsFileTime`.
This commit is contained in:
Schrodinger ZHU Yifan 2024-12-05 14:08:27 -05:00 committed by GitHub
parent 51a5b77b57
commit 0adff0af20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 248 additions and 47 deletions

View File

@ -95,6 +95,9 @@ set(TARGET_LIBC_ENTRYPOINTS
# errno.h entrypoints
libc.src.errno.errno
# time.h entrypoints
libc.src.time.time
)
set(TARGET_LIBM_ENTRYPOINTS

View File

@ -135,10 +135,18 @@ add_proxy_header_library(
libc.include.llvm-libc-macros.unistd_macros
)
if (WIN32)
set(windows_addtional_time_macros libc.include.llvm-libc-macros.windows.time_macros_ext)
else()
set(windows_addtional_time_macros "")
endif()
add_proxy_header_library(
time_macros
HDRS
time_macros.h
DEPENDS
${windows_addtional_time_macros}
FULL_BUILD_DEPENDS
libc.include.time
libc.include.llvm-libc-macros.time_macros

View File

@ -19,4 +19,10 @@
#endif // LLVM_LIBC_FULL_BUILD
// TODO: For now, on windows, let's always include the extension header.
// We will need to decide how to export this header.
#ifdef _WIN32
#include "include/llvm-libc-macros/windows/time-macros-ext.h"
#endif // _WIN32
#endif // LLVM_LIBC_HDR_TIME_MACROS_H

View File

@ -9,7 +9,8 @@
#ifndef LLVM_LIBC_HDR_TYPES_CLOCKID_T_H
#define LLVM_LIBC_HDR_TYPES_CLOCKID_T_H
#ifdef LIBC_FULL_BUILD
// TODO: we will need to decide how to export extension to windows.
#if defined(LIBC_FULL_BUILD) || defined(_WIN32)
#include "include/llvm-libc-types/clockid_t.h"

View File

@ -0,0 +1,6 @@
add_header(
time_macros_ext
HDR
time-macros-ext.h
)

View File

@ -0,0 +1,17 @@
//===-- Windows Time Macros Extension -------------------------------------===//
//
// 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_MACROS_WINDOWS_TIME_MACROS_EXT_H
#define LLVM_LIBC_MACROS_WINDOWS_TIME_MACROS_EXT_H
#define CLOCK_MONOTONIC 0
#define CLOCK_REALTIME 1
#define CLOCK_PROCESS_CPUTIME_ID 2
#define CLOCK_THREAD_CPUTIME_ID 3
#endif // LLVM_LIBC_MACROS_WINDOWS_TIME_MACROS_EXT_H

View File

@ -13,6 +13,7 @@ add_subdirectory(stdio)
add_subdirectory(stdlib)
add_subdirectory(string)
add_subdirectory(wchar)
add_subdirectory(time)
if(${LIBC_TARGET_OS} STREQUAL "linux")
add_subdirectory(dirent)
@ -40,5 +41,4 @@ add_subdirectory(setjmp)
add_subdirectory(signal)
add_subdirectory(spawn)
add_subdirectory(threads)
add_subdirectory(time)
add_subdirectory(locale)

View File

@ -2,6 +2,13 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${LIBC_TARGET_OS})
endif()
add_object_library(
clock_gettime
ALIAS
DEPENDS
libc.src.__support.time.${LIBC_TARGET_OS}.clock_gettime
)
add_header_library(
units
HDRS

View File

@ -6,21 +6,17 @@
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
#ifndef LLVM_LIBC_SRC___SUPPORT_TIME_CLOCK_GETTIME_H
#define LLVM_LIBC_SRC___SUPPORT_TIME_CLOCK_GETTIME_H
#include "hdr/types/clockid_t.h"
#include "hdr/types/struct_timespec.h"
#include "src/__support/error_or.h"
#if defined(SYS_clock_gettime64)
#include <linux/time_types.h>
#endif
namespace LIBC_NAMESPACE_DECL {
namespace internal {
ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts);
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_GETTIME_H
#endif // LLVM_LIBC_SRC___SUPPORT_TIME_CLOCK_GETTIME_H

View File

@ -1,7 +1,7 @@
add_object_library(
clock_gettime
HDRS
clock_gettime.h
../clock_gettime.h
SRCS
clock_gettime.cpp
DEPENDS

View File

@ -10,7 +10,7 @@
#define LLVM_LIBC_SRC___SUPPORT_TIME_LINUX_CLOCK_CONVERSION_H
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/__support/time/units.h"
namespace LIBC_NAMESPACE_DECL {

View File

@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "hdr/types/clockid_t.h"
#include "hdr/types/struct_timespec.h"
#include "src/__support/OSUtil/linux/vdso.h"

View File

@ -0,0 +1,16 @@
add_object_library(
clock_gettime
HDRS
../clock_gettime.h
SRCS
clock_gettime.cpp
DEPENDS
libc.hdr.types.struct_timespec
libc.hdr.types.clockid_t
libc.hdr.errno_macros
libc.src.__support.common
libc.src.__support.error_or
libc.src.__support.OSUtil.osutil
libc.src.__support.CPP.atomic
libc.src.__support.CPP.limits
)

View File

@ -0,0 +1,150 @@
//===--- clock_gettime windows implementation -------------------*- 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/time_macros.h"
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/bit.h"
#include "src/__support/CPP/limits.h"
#include "src/__support/macros/optimization.h"
#include "src/__support/time/clock_gettime.h"
#include "src/__support/time/units.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;
constexpr long long SEC_LIMIT =
cpp::numeric_limits<decltype(ts->tv_sec)>::max();
ErrorOr<int> ret = 0;
switch (clockid) {
default:
ret = cpp::unexpected(EINVAL);
break;
case CLOCK_MONOTONIC: {
// 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.
[[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 ticks = buffer.QuadPart;
long long tv_sec = ticks / freq;
long long tv_nsec = (ticks % freq) * 1_s_ns / freq;
if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) {
ret = cpp::unexpected(EOVERFLOW);
break;
}
ts->tv_sec = static_cast<decltype(ts->tv_sec)>(tv_sec);
ts->tv_nsec = static_cast<decltype(ts->tv_nsec)>(tv_nsec);
break;
}
case CLOCK_REALTIME: {
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
// GetSystemTimePreciseAsFileTime
// This function is best suited for high-resolution time-of-day
// measurements, or time stamps that are synchronized to UTC
[[clang::uninitialized]] FILETIME file_time;
[[clang::uninitialized]] ULARGE_INTEGER time;
::GetSystemTimePreciseAsFileTime(&file_time);
time.LowPart = file_time.dwLowDateTime;
time.HighPart = file_time.dwHighDateTime;
// adjust to POSIX epoch (from Jan 1, 1601 to Jan 1, 1970)
constexpr unsigned long long POSIX_TIME_SHIFT =
(11644473600ULL * HNS_PER_SEC);
if (LIBC_UNLIKELY(POSIX_TIME_SHIFT > time.QuadPart)) {
ret = cpp::unexpected(EOVERFLOW);
break;
}
time.QuadPart -= (11644473600ULL * HNS_PER_SEC);
unsigned long long tv_sec = time.QuadPart / HNS_PER_SEC;
unsigned long long tv_nsec = (time.QuadPart % HNS_PER_SEC) * 100ULL;
if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) {
ret = cpp::unexpected(EOVERFLOW);
break;
}
ts->tv_sec = static_cast<decltype(ts->tv_sec)>(tv_sec);
ts->tv_nsec = static_cast<decltype(ts->tv_nsec)>(tv_nsec);
break;
}
case CLOCK_PROCESS_CPUTIME_ID:
case CLOCK_THREAD_CPUTIME_ID: {
[[clang::uninitialized]] FILETIME creation_time;
[[clang::uninitialized]] FILETIME exit_time;
[[clang::uninitialized]] FILETIME kernel_time;
[[clang::uninitialized]] FILETIME user_time;
bool success;
if (clockid == CLOCK_PROCESS_CPUTIME_ID) {
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes
success = ::GetProcessTimes(::GetCurrentProcess(), &creation_time,
&exit_time, &kernel_time, &user_time);
} else {
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes
success = ::GetThreadTimes(::GetCurrentThread(), &creation_time,
&exit_time, &kernel_time, &user_time);
}
if (!success) {
ret = cpp::unexpected(EINVAL);
break;
}
// https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
// It is not recommended that you add and subtract values from the FILETIME
// structure to obtain relative times. Instead, you should copy the low- and
// high-order parts of the file time to a ULARGE_INTEGER structure, perform
// 64-bit arithmetic on the QuadPart member, and copy the LowPart and
// HighPart members into the FILETIME structure.
auto kernel_time_hns = cpp::bit_cast<ULARGE_INTEGER>(kernel_time);
auto user_time_hns = cpp::bit_cast<ULARGE_INTEGER>(user_time);
unsigned long long total_time_hns =
kernel_time_hns.QuadPart + user_time_hns.QuadPart;
unsigned long long tv_sec = total_time_hns / HNS_PER_SEC;
unsigned long long tv_nsec = (total_time_hns % HNS_PER_SEC) * 100ULL;
if (LIBC_UNLIKELY(tv_sec > SEC_LIMIT)) {
ret = cpp::unexpected(EOVERFLOW);
break;
}
ts->tv_sec = static_cast<decltype(ts->tv_sec)>(tv_sec);
ts->tv_nsec = static_cast<decltype(ts->tv_nsec)>(tv_nsec);
break;
}
}
return ret;
}
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL

View File

@ -106,9 +106,15 @@ add_entrypoint_object(
add_entrypoint_object(
time
ALIAS
SRCS
time.cpp
HDRS
time_func.h
DEPENDS
.${LIBC_TARGET_OS}.time
libc.hdr.time_macros
libc.hdr.types.time_t
libc.src.__support.time.clock_gettime
libc.src.errno.errno
)
add_entrypoint_object(

View File

@ -1,16 +1,3 @@
add_entrypoint_object(
time
SRCS
time.cpp
HDRS
../time_func.h
DEPENDS
libc.hdr.time_macros
libc.hdr.types.time_t
libc.src.__support.time.linux.clock_gettime
libc.src.errno.errno
)
add_entrypoint_object(
timespec_get
SRCS
@ -20,7 +7,7 @@ add_entrypoint_object(
DEPENDS
libc.hdr.time_macros
libc.hdr.types.struct_timespec
libc.src.__support.time.linux.clock_gettime
libc.src.__support.time.clock_gettime
libc.src.errno.errno
)
@ -34,7 +21,7 @@ add_entrypoint_object(
libc.hdr.time_macros
libc.hdr.types.clock_t
libc.src.__support.time.units
libc.src.__support.time.linux.clock_gettime
libc.src.__support.time.clock_gettime
libc.src.__support.CPP.limits
libc.src.errno.errno
)
@ -62,7 +49,7 @@ add_entrypoint_object(
DEPENDS
libc.hdr.types.clockid_t
libc.hdr.types.struct_timespec
libc.src.__support.time.linux.clock_gettime
libc.src.__support.time.clock_gettime
libc.src.errno.errno
)
@ -75,7 +62,7 @@ add_entrypoint_object(
DEPENDS
libc.hdr.time_macros
libc.hdr.types.suseconds_t
libc.src.__support.time.linux.clock_gettime
libc.src.__support.time.clock_gettime
libc.src.__support.time.units
libc.src.errno.errno
)

View File

@ -11,7 +11,7 @@
#include "src/__support/CPP/limits.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/__support/time/units.h"
#include "src/errno/libc_errno.h"

View File

@ -9,7 +9,7 @@
#include "src/time/clock_gettime.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/errno/libc_errno.h"
namespace LIBC_NAMESPACE_DECL {

View File

@ -11,7 +11,7 @@
#include "hdr/types/suseconds_t.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/__support/time/units.h"
#include "src/errno/libc_errno.h"

View File

@ -10,7 +10,7 @@
#include "hdr/time_macros.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/errno/libc_errno.h"
namespace LIBC_NAMESPACE_DECL {

View File

@ -9,14 +9,14 @@
#include "hdr/time_macros.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/errno/libc_errno.h"
#include "src/time/time_func.h"
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(time_t, time, (time_t * tp)) {
// TODO: Use the Linux VDSO to fetch the time and avoid the syscall.
// avoid inconsitent clang-format behavior
using time_ptr_t = time_t *;
LLVM_LIBC_FUNCTION(time_t, time, (time_ptr_t tp)) {
struct timespec ts;
auto result = internal::clock_gettime(CLOCK_REALTIME, &ts);
if (!result.has_value()) {

View File

@ -59,6 +59,7 @@ add_subdirectory(stdio)
add_subdirectory(stdlib)
add_subdirectory(string)
add_subdirectory(wchar)
add_subdirectory(time)
# Depends on utilities in stdlib
add_subdirectory(inttypes)
@ -82,7 +83,6 @@ add_subdirectory(network)
add_subdirectory(setjmp)
add_subdirectory(signal)
add_subdirectory(spawn)
add_subdirectory(time)
add_subdirectory(locale)
if(${LIBC_TARGET_OS} STREQUAL "linux")

View File

@ -12,7 +12,7 @@
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/threads/linux/raw_mutex.h"
#include "src/__support/threads/sleep.h"
#include "src/__support/time/linux/clock_gettime.h"
#include "src/__support/time/clock_gettime.h"
#include "src/stdlib/exit.h"
#include "src/sys/mman/mmap.h"
#include "src/sys/mman/munmap.h"

View File

@ -157,8 +157,8 @@ add_libc_unittest(
SRCS
time_test.cpp
DEPENDS
libc.include.time
libc.src.time.time
libc.src.__support.time.clock_gettime
libc.src.errno.errno
)

View File

@ -9,8 +9,6 @@
#include "src/time/time_func.h"
#include "test/UnitTest/Test.h"
#include <time.h>
TEST(LlvmLibcTimeTest, SmokeTest) {
time_t t1;
time_t t2 = LIBC_NAMESPACE::time(&t1);

View File

@ -1471,13 +1471,13 @@ libc_support_library(
}),
deps = [
":__support_time",
":__support_time_linux_clock_gettime",
":__support_time_clock_gettime",
],
)
libc_support_library(
name = "__support_time_linux_clock_gettime",
hdrs = ["src/__support/time/linux/clock_gettime.h"],
name = "__support_time_clock_gettime",
hdrs = ["src/__support/time/clock_gettime.h"],
target_compatible_with = select({
"@platforms//os:linux": [],
"//conditions:default": ["@platforms//:incompatible"],